pthread_mutex
POSIX threads(简称Pthreads)定义了一套跨平台的多线程常用API,线程同步在并行编程中非常重要的,其中最典型的应用就是用Pthreads提供的锁机制来对多个线程之间共享临界区进行保护。
Pthreads锁的常见用法;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化属性
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定义锁的属性,定义成常规锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr) // 创建锁
pthread_mutex_lock(&mutex); // 申请锁
// 临界区代码
pthread_mutex_unlock(&mutex); // 释放锁
从上面的代码中我们很容易就明白其用法这里不在详述,我们下面着重对锁的类型进行介绍。在方法pthread_mutexattr_settype
中,我们将第二个参数设置为PTHREAD_MUTEX_NORMAL,这是将锁设置为普通锁,不做死锁检测。有可能造成死锁,实际上它还可以有其他几种类型。
- PTHREAD_MUTEX_NORMAL
如果mutex的type被置为PTHREAD_MUTEX_NORMAL
,则系统将不会为他提供死锁检测,尝试对已经加锁的mutex进行加锁操作的时候,将会造成死锁。如果一个线程试图解锁一个还没有加锁或者已经被解锁的mutex进行解锁操作的时候,会发生不可预测问题。 - PTHREAD_MUTEX_ERRORCHECK
如果mutex的type被置为PTHREAD_MUTEX_ERRORCHECK
,则系统将会为他提供错误检测。尝试对已经加锁的mutex进行加锁操作的时候会返回错误。如果一个线程试图解锁一个还没有加锁或者已经被解锁的mutex进行解锁操作的时候,也会返回错误。 - PTHREAD_MUTEX_RECURSIVE(递归锁)
如果mutex的type被置为PTHREAD_MUTEX_RECURSIVE
,mutex会维护一个加锁次数的变量。当一个线程第一次成功给mutex加锁后,加锁次数将被设置为1.线程每次对mutex加锁,加锁次数加1,每次解锁加锁次数减1,当加锁次数变为0时,其他线程
就可以对mutex加锁了。如果一个线程试图解锁一个还没有加锁或者已经被解锁的mutex进行解锁操作的时候,会返回错误。 - PTHREAD_MUTEX_DEFAULT
如果mutex的type被置为PTHREAD_MUTEX_DEFAULT
,尝试递归的给mutex加锁会导致不可预知的错误,如果一个线程试图解锁一个还没有加锁或者已经被解锁的mutex进行解锁操作的时候也会返回不可预知的错误。
其中PTHREAD_MUTEX_NORMAL
,PTHREAD_MUTEX_ERRORCHECK
,PTHREAD_MUTEX_DEFAULT
定义的是互斥锁,PTHREAD_MUTEX_RECURSIVE
定义的是递归锁。
一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致不可预知错误。假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁。
然而这种情况经常会发生,比如某个函数申请了锁,在临界区内又递归调用了自己,递归锁就可以解决这种问题。递归锁的简单实现可以看这里
自旋锁
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
从实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过
pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞(blocking),Core0会在此时进行上下文切换(Context Switch)将线程A置于
等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。
因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。
自旋锁的简单实现逻辑可以查看这里
自旋锁
参考资料:
1.https://bestswifter.com/ios-lock/
2.https://www.cnblogs.com/zendu/p/5387596.html
3.http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_mutex_lock.html
4.http://blog.sina.com.cn/s/blog_7c6086150101a30y.html