Linux下的互斥锁、自旋锁、读写锁浅谈

自旋锁

概念:

何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。在任何时刻最多只能有一个执行单元获得锁,即任何时刻只有一个线程访问对象。如果自旋锁已经被别的执行单元保持(资源被占用)调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。自旋锁不会引起调用者睡眠,"自旋"一词就是因此而得名。

原理:

跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。但会存在两个问题:

死锁。试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。

互斥锁

概念:

在编程中,我们引入互斥锁,来保证对共享资源的访问唯一性。在任意时刻,只有一个线程可访问它。和自选锁一样,是用来来完成”线程同步”。

API

创建: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。

获取锁:int pthread_mutex_lock(pthread_mutex_t *mutex)  在锁已经被占据时返回挂起等待

销毁int pthread_mutex_destroy(pthread_mutex_t *mutex)

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。下一步释放锁。。

释放锁:int pthread_mutex_unlock(pthread_mutex_t *mutex)

*非阻塞加锁:int pthread_mutex_trylock(pthread_mutex_t *mutex)  在锁已经被占据时返回EBUSY.

 互斥锁属性

互斥锁的属性在创建锁的时候指定,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:

* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

两种锁适用于不同场景
如果是多核处理器,如果预计线程等待锁的时间很短,短到比线程两次上下文切换时间要少的情况下,使用自旋锁是划算的。
如果是多核处理器,如果预计线程等待锁的时间较长,至少比两次线程上下文切换的时间要长,建议使用互斥量。
如果是单核处理器,一般建议不要使用自旋锁。因为,在同一时间只有一个线程是处在运行状态,那如果运行线程发现无法获取锁,只能等待解锁,但因为自身不挂起,所以那个获取到锁的线程没有办法进入运行状态,只能等到运行线程把操作系统分给它的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。
如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。

读写锁

概念:

读写锁实际是一种特殊的自旋锁,这组锁它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

因为读写锁保持期间也是抢占失效的。如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。

特性:

一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁、

① 当读写锁是写加锁(独占)状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞

②当读写锁在读加锁(共享)状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权。但是如果线程希望以写模式对此锁进行加锁, 它必须直到所有的线程释放锁,并且读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞.

API 

#include <pthread.h>

创建:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

获取读锁:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

获取写锁:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

销毁锁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

  同互斥量一样, 在释放读写锁占用的内存之前, 需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.

释放锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

总结:

读写锁适合于对数据结构读次数多,而写次数少的情况。因为读加锁时可以共享“共享资源”,而写加锁时独占“共享资源”。

所以读写锁又叫“共享-独占锁”。

猜你喜欢

转载自blog.csdn.net/qq_19525389/article/details/81434917