Posix线程的锁

本文介绍了Posix线程中的锁机制,包括互斥锁(Mutex)、条件锁(Cond)、自旋锁、读写锁和递归锁。互斥锁用于保护共享资源的互斥访问,条件锁用于线程在满足特定条件时阻塞和唤醒。自旋锁在持有锁的线程释放前,会让请求锁的线程持续检查。读写锁允许多个读者同时访问,但写者独占资源。递归锁则允许同一线程多次获取,防止死锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Posix线程中的锁

pthread是POSIX threads的简称,是POSIX的线程标准
锁是实现线程同步的一种方式

1、互斥锁(或称互斥量Mutex)

互斥锁是用于控制多个线程对他们之间共享资源互斥访问的一个信号量。

头文件:<pthread.h>

类型:pthread_mutex_t

函数:

pthread_mutex_init(pthread_mutex_t * mutex, const phtread_mutexattr_t * mutexattr);//动态方式创建锁,相当于new动态创建一个对象  
pthread_mutex_destory(pthread_mutex_t *mutex)        //释放互斥锁,相当于delete
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;   //以静态方式创建锁
pthread_mutex_lock(pthread_mutex_t *mutex)           //以阻塞方式运行的。如果之前mutex被加锁了,那么程序会阻塞在这里。
pthread_mutex_unlock(pthread_mutex_t *mutex)		 //解锁
int pthread_mutex_trylock(pthread_mutex_t * mutex);  //会尝试对mutex加锁。如果mutex之前已经被锁定,返回非0;如果mutex没有被锁定,则函数返回并锁定mutex。该函数是以非阻塞方式运行了。也就是说如果mutex之前已经被锁定,函数会返回非0,程序继续往下执行。
2、条件锁(或称条件变量Cond)

条件锁就是所谓的条件变量,某一个线程因为某个条件为满足时可以使用条件变量使改程序处于阻塞状态。一旦条件满足以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。这个过程中就使用到了条件变量pthread_cond_t。

头文件:<pthread.h>

类型:pthread_cond_t

函数:

pthread_cond_init(pthread_cond_t * condtion, const phtread_condattr_t * condattr);	//对条件变量进行动态初始化,相当于new创建对象
pthread_cond_destory(pthread_cond_t * condition);									//释放动态申请的条件变量,相当于delete释放对象
pthread_cond_t condition = PTHREAD_COND_INITIALIZER;								//静态初始化条件变量
pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);					//该函数以阻塞方式执行。如果某个线程中的程序执行了该函数,那么这个线程就会以阻塞方式等待,直到收到pthread_cond_signal或者pthread_cond_broadcast函数发来的信号而被唤醒。
pthread_cond_signal(pthread_cond_t * cond);											//在另外一个线程中改变线程,条件满足发送信号。唤醒一个等待的线程(可能有多个线程处于阻塞状态),唤醒哪个线程由具体的线程调度策略决定
pthread_cond_broadcast(pthread_cond_t * cond);										//以广播形式唤醒所有因为该条件变量而阻塞的所有线程,唤醒哪个线程由具体的线程调度策略决定
pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, struct timespec * time);//以阻塞方式等待,如果时间time到了条件还没有满足还是会结束
3、自旋锁

前面的两种锁是比较常见的锁,也比较容易理解。下面通过比较互斥锁和自旋锁原理的不同,这对于真正理解自旋锁有很大帮助。

假设我们有一个两个处理器core1和core2计算机,现在在这台计算机上运行的程序中有两个线程:T1和T2分别在处理器core1和core2上运行,两个线程之间共享着一个资源。

首先我们说明互斥锁的工作原理,互斥锁是一种sleep-waiting的锁。假设线程T1获取互斥锁并且正在core1上运行时,此时线程T2也想要获取互斥锁(pthread_mutex_lock),但是由于T1正在使用互斥锁使得T2被阻塞。当T2处于阻塞状态时,T2被放入到等待队列中去,处理器core2会去处理其他任务而不必一直等待(忙等)。也就是说处理器不会因为线程阻塞而空闲着,它去处理其他事务去了。

而自旋锁就不同了,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用自旋锁,而T2也去申请这个自旋锁,此时T2肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2的处理器core2会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。

从“自旋锁”的名字也可以看出来,如果一个线程想要获取一个被使用的自旋锁,那么它会一致占用CPU请求这个自旋锁使得CPU不能去做其他的事情,直到获取这个锁为止,这就是“自旋”的含义。

当发生阻塞时,互斥锁可以让CPU去处理其他的任务;而自旋锁让CPU一直不断循环请求获取这个锁。通过两个含义的对比可以我们知道“自旋锁”是比较耗费CPU的

头文件:<linux\spinlock.h>

自旋锁的类型:spinlock_t

相关函数:

spin_lock_init(spinlock_t *x);  //初始化
spin_lock(x);  					//只有在获得锁的情况下才返回,否则一直“自旋”  
spin_trylock(x);  				//如立即获得锁则返回真,否则立即返回假  
spin_unlock(x);  				//释放锁
spin_is_locked(x) 				//该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是,返回真,否则返回假。

注意:自旋锁适合于短时间的的轻量级的加锁机制。

4、读写锁

说到读写锁我们可以借助于“读者-写者”问题进行理解。首先我们简单说下“读者-写者”问题。

计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是一个简单的读者-写者模型。

pthread读写锁把对共享资源的访问分为读者和写者,读者只对共享资源进行读访问,写者只对共享资源进行写操作。
在互斥机制下,读者和写者都需要独立独占互斥量以独占共享资源;
在读写锁机制下,允许同时有多个读者访问共享资源,只有写者才需要独占资源。
相比互斥机制,读写机制由于允许多个读者同时访问共享资源,进一步提高了多线程的并发度。

4.1、 读写锁机制
写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。

读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

4.2、 读写锁特性
同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁;

读写锁处于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。

读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁。

4.3、 读写锁基本函数

头文件:<pthread.h>

类型:pthread_rwlock\t

函数:

int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr); //动态读写锁初始化,该函数第一个参数为读写锁指针,第二个参数为读写锁属性指针。函数按读写锁属性对读写锁进行初始化。
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);	//可用来销毁 rwlock 引用的读写锁对象并释放该锁使用的任何资源。
pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;  //静态读写锁初始化
int  pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );	//向 rwlock 所引用的读写锁加读锁。为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);	//应用读锁的方式与 pthread_rwlock_rdlock() 类似,区别在于如果任何线程持有 rwlock 中的写锁或者写入器基于 rwlock 阻塞,则 pthread_rwlock_tryrdlock() 函数会失败。
int  pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );	//如果没有其他读取器线程或写入器线程持有读写锁 rwlock,则调用线程将获取写锁。否则,调用线程将阻塞。调用线程必须获取该锁之后,才能从pthread_rwlock_wrlock() 调用返回。如果在进行调用时,调用线程持有读写锁(读锁或写锁),则结果是不确定的。
int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);//应用写锁的方式与 pthread_rwlock_wrlock() 类似,区别在于如果任何线程当前持有用于读取和写入的 rwlock,则pthread_rwlock_trywrlock() 函数会失败。
int pthread_rwlock_unlock (pthread_rwlock_t  *rwlock);  //用来释放在 rwlock 引用的读写锁对象中持有的锁。

为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器

5、递归锁

Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。

二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。

Windows下的Mutex和Critical Section是可递归的。Linux下的pthread_mutex_t锁默认是非递归的。可以显示的设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁。

本文参考链接:
https://www.cnblogs.com/x_wukong/p/5671537.html
https://blog.youkuaiyun.com/zouxinfox/article/details/5838861

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值