C语言多线程死锁成因分析与读写锁实战
在C语言多线程编程中,资源竞争引发的死锁是常见痛点,而使用读写锁(Read-Write Lock)能有效提升读取密集型场景的性能。本文将解析死锁的典型成因与复现代码,并演示如何通过读写锁实现高效的并发数据访问。
死锁现象与成因分析
当两个或更多线程在等待对方持有的资源时,若无外力干预,系统将陷入永久等待状态,即死锁。在C语言多线程开发中,以下三种情况最容易触发死锁:
- 忘记释放锁:线程获取互斥量后未执行解锁操作,导致后续线程无法获取资源。
- 重复加锁:同一线程对非递归互斥量进行二次加锁,会导致自身卡死。
- 多锁抢占多个线程以不同顺序锁定同一组互斥量,形成环形等待。
以下代码展示了典型的“多线程多锁抢占”场景:线程A先锁mutex1再锁mutex2,而线程B先锁mutex2再锁mutex1。这种逆序加锁极易引发死锁。
// 多线程多锁,抢占锁资源
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;
void * workA(void * arg) {
pthread_mutex_lock(&mutex1);
sleep(1); // 故意延时增加死锁概率
pthread_mutex_lock(&mutex2);
printf("workA....\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void * workB(void * arg) {
pthread_mutex_lock(&mutex2);
sleep(1);
pthread_mutex_lock(&mutex1);
printf("workB....\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
int main() {
// 初始化互斥量
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);
// 创建2个子线程
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, workA, NULL);
pthread_create(&tid2, NULL, workB, NULL);
// 回收子线程资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 释放互斥量资源
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}运行该程序后,由于线程A和线程B互相持有对方需要的锁且不再释放,程序将挂起无法继续执行。
读写锁优化并发效率
为了在保证数据一致性的同时提高读取效率,可以使用读写锁(pthread_rwlock_t)。其核心逻辑为:读时共享,写时独占。即允许同时存在多个读者,但写入者必须独占锁,期间所有读者需等待。
以下案例展示如何使用读写锁让3个写线程和5个读线程并发操作全局变量num:
/*
读写锁的类型 pthread_rwlock_t
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 创建一个共享数据
int num = 1;
pthread_rwlock_t rwlock;
void * writeNum(void * arg) {
while(1) {
pthread_rwlock_wrlock(&rwlock); // 获取写锁
num++;
printf("++write, tid : %ld, num : %d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
void * readNum(void * arg) {
while(1) {
pthread_rwlock_rdlock(&rwlock); // 获取读锁
printf("===read, tid : %ld, num : %d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
// 创建3个写线程,5个读线程
pthread_t wtids[3], rtids[5];
for(int i = 0; i < 3; i++) {
pthread_create(&wtids[i], NULL, writeNum, NULL);
}
for(int i = 0; i < 5; i++) {
pthread_create(&rtids[i], NULL, readNum, NULL);
}
// 设置线程分离
for(int i = 0; i < 3; i++) {
pthread_detach(wtids[i]);
}
for(int i = 0; i < 5; i++) {
pthread_detach(rtids[i]);
}
// 主线程等待任意线程退出即可结束,这里直接等待一段时间看效果
sleep(2);
pthread_exit(NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}在该场景下,由于读线程之间不会互相阻塞,系统的整体吞吐量将显著高于使用普通互斥锁的情况。
以上就是C语言多线程开发中死锁与读写锁问题的详细内容,更多关于C语言并发编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













