互斥锁、递归锁、读写锁、自旋锁、条件锁;及pthread实现

本文详细介绍了多线程同步中的互斥锁、递归锁、读写锁、自旋锁和条件锁,并提供了pthread库的实现示例。互斥锁确保资源的独占使用,递归锁允许同一线程多次加锁,读写锁允许多个读线程并行,自旋锁在资源被占用时循环等待,条件锁则支持线程根据特定条件等待和唤醒。这些锁机制在多线程编程中起到关键的同步和协调作用。

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

互斥锁、递归锁、读写锁、自旋锁、条件锁;及pthread实现

互斥锁

共享资源的使用是互斥的,即一个线程获得资源的使用权后就会将改资源加锁,使用完后会将其解锁,所以在使用过程中有其它线程想要获取该资源的锁,那么它就会被阻塞陷入睡眠状态,直到该资源被解锁才会别唤醒,如果被阻塞的资源不止一个,那么它们都会被唤醒,但是获得资源使用权的是第一个被唤醒的线程,其它线程又陷入沉睡。

pthread实现:

pthread_mutex_t lock;

// 初始化互斥锁
pthread_mutex_init(&lock, NULL);

// 销毁互斥锁
pthread_mutex_destroy(&lock);

// 加锁
pthread_mutex_lock(&lock);

// 解锁
pthread_mutex_unlock(&lock);

递归锁

同一个线程可以多次获得该资源锁,别的线程必须等待该线程释放所有次数的锁才能获得。

pthread实现:

pthread_mutex_t lock;

// 初始化递归锁
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);

// 销毁递归锁
int pthread_mutex_destroy(&lock);

// 加锁
int pthread_mutex_lock(&lock);

// 解锁
int pthread_mutex_unlock(&lock);

读写锁

读写锁拥有读状态加锁、写状态加锁、不加锁三种状态。只有一个线程可以占有写状态的锁,但可以多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

读写锁三个特征:

  • 多个读者可以同时进行读
  • 写者必须互斥,只允许一个写者写,也不能读者写者同时进行
  • 写者优先于读者,一旦有写者,则后续读者必须等待,唤醒时优先考虑写者

pthread实现:

pthread_rwlock_t lock;

// 初始化读写锁
pthread_rwlock_init(&lock, NULL);

// 销毁读写锁
pthread_rwlock_destroy(&lock);

// 读状态加锁
pthread_rwlock_rdlock(&lock);

// 写状态加锁
pthread_rwlock_wrlock(&lock);

// 解锁
pthread_rwlock_unlock(&lock);

自旋锁

自旋锁是一种特殊的互斥锁,当资源被加锁后,其它线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能再做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU资源。适用于资源的锁被持有的时间短,而不希望在线程的唤醒上花费太多资源的情况。

自旋锁的目的
自旋锁的实现是为了保护一段短小的临界区操作代码,保证这个临界区的操作是原子的,从而避免并发的竞争冒险。在Linux内核中,自旋锁通常用于包含内核数据结构的操作,你可以看到许多内核数据结构中都嵌入有spinlock,这些大部分就是用于保护它自身被操作的原子性,在操作这样的结构体时都经历这样的过程:上锁-操作-解锁。

如果内核控制路径发现自旋锁“开着”(可以获取),就获取并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在原地“旋转”,反复执行一条紧凑的循环检测指令,直到锁被被释放。自旋锁是循环检测“忙等”,即等待时内核无事可做,进程在CPU上保持运行,所以它的临界区必须小,且操作过程必须短。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段,所以等待自旋锁的释放不会消耗太多CPU的时间。

如果系统中的pthread库支持spinlock那么可以用下面的实现,一般Linux系统的pthread都支持spinlock,Macos的pthread不支持spinlock。

pthread_spinlock_t lock;

// 初始化
pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);

// 销毁
pthread_spin_destroy(&lock);

// 加锁
pthread_spin_lock(&lock);

// 解锁
pthread_spin_unlock(&lock);

条件锁

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

pthread实现:

pthread_cond_t cond;
pthread_mutex_t cond_lock = PTHREAD_MUTEX_INITIALIZER;

// 初始化
pthread_cond_init(&cond, NULL);

// 销毁
pthread_cond_destroy(&cond);

// 等待条件
pthread_cond_wait(&cond, &cond_lock); // 如果某个线程中的程序执行了该函数,那么这个线程就会以阻塞方式等待,直到收到pthread_cond_signal或者pthread_cond_broadcast函数发来的信号而被唤醒

// 带超时的等待
pthread_cond_timedwait(&cond, &cond_lock, time);//以阻塞方式等待,如果时间time到了条件还没有满足还是会结束

// 唤醒一个
pthread_cond_signal(&cond); // 唤醒一个等待的线程(可能有多个线程处于阻塞状态),唤醒哪个线程由具体的线程调度策略决定

// 唤醒所有
pthread_cond_broadcast(&cond);

注意:调用pthread_cond_wait之前之后需要互斥量加锁,最后还要对互斥量解锁。 示例代码:

pthread_mutex_lock(mutex);//加互斥锁
pthread_cond_wait(cond, mutex);//解锁,其他线程使条件成立发送信号,加锁。
...//对进程之间的共享资源进行操作
pthread_mutex_unlock(mutex);//释放互斥锁

另外,为了防止“虚假唤醒”,通常会将pthread_cond_wait放入循环中,如下:

pthread_mutex_lock(mutex);//加互斥锁
for (;;) {
	pthread_cond_wait(cond, mutex);//解锁,其他线程使条件成立发送信号,加锁。
	if (条件成立) break;  // 当条件成立会退出循环,否则(虚假唤醒)会继续下一次wait。
}
...//对进程之间的共享资源进行操作
pthread_mutex_unlock(mutex);//释放互斥锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空旅客er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值