【Linux多线程编程】6. 线程锁(3)——条件变量
admin
2024-05-08 10:37:33
0

前言

上篇文章【Linux多线程编程】5. 线程锁(2)——死锁、读写锁介绍了几种能够引发死锁的场景,并介绍了何为读写锁,以及读写锁的使用场景。本文首要先描述读写锁的基本使用,然后介绍第三种线程同步方式——条件变量。

读写锁的使用

  1. 创建读写锁
    pthread_rwlock_t rwlock;
    
  2. 初始化读写锁
    初始化读写锁的方法有两种,一种是直接将 PTHREAD_RWLOCK_INITIALIZER 宏赋值给读写锁变量,一种是通过函数初始化
    pthread_rwlock_t myRWLock = PTHREAD_RWLOCK_INITIALIZER; // 方法1,直接赋值
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    // 方法2:函数调用 
    // 参数:
    // rwlock 参数用于指定要初始化的读写锁变量;
    // attr 参数用于自定义读写锁变量的属性,置为 NULL 时表示以默认属性初始化读写锁。
    // 一般 attr 参数传NULL
    
  3. 释放读写锁资源
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
  4. 加锁
    int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock); // 加读锁
    int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock); // 加写锁
    int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock); 
    int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock); 
    
    pthread_rwlock_tryxx 与 pthread_rwlock_rdlock/pthread_rwlock_wrlock 的区别在于:

    若此时无法加对应的锁,则 pthread_rwlock_rdlock/pthread_rwlock_wrlock 会阻塞等待锁被释放
    pthread_rwlock_tryxx 则是直接返回EBUSY 错误码,不会阻塞线程。

  5. 解锁
    int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
    
  6. 锁的销毁
    int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
    

读写锁代码示例

创建3个读线程,1个写线程,读线程输出临界资源 x 的值,写线程对临界资源 x 的值 + 1,并输出写后的值。
因此,读线程的工作函数 read_thread 调用 pthread_rwlock_rdlock加读锁。
写线程的工作函数 write_thread 调用 pthread_rwlock_wrlock 加写锁。

// thread.c
#include 
#include 
#include 
int x = 0; // 临界资源 x
//创建读写锁变量
pthread_rwlock_t myrwlock;// 读线程,只加读锁即可
void* read_thread(void* args){printf("------%u read_thread ready\n", pthread_self()); // pthread_self() 输出当前线程的线程IDwhile (1) {sleep(1);//请求读锁pthread_rwlock_rdlock(&myrwlock);printf("read_thread: %u,x=%d\n", pthread_self(), x);sleep(1);//释放读写锁pthread_rwlock_unlock(&myrwlock);}return NULL;
}// 写线程,需要加写锁
void* write_thread(void* param)
{printf("------%u write_thread ready!\n",pthread_self());while (1) {sleep(1);// 请求写锁pthread_rwlock_wrlock(&myrwlock);++x; // 修改临界资源 x 的值,需要加写锁printf("write_thread: %u,x=%d\n", pthread_self(), x);sleep(1);//释放读写锁pthread_rwlock_unlock(&myrwlock);}return NULL;
}int main()
{int i;//初始化读写锁pthread_rwlock_init(&myrwlock, NULL);//创建 3 个读 x 变量的线程pthread_t readThread[3];for (i = 0; i < 3; ++i) {pthread_create(&readThread[i], NULL, read_thread, NULL);}//创建 1 个修改 x 变量的线程pthread_t writeThread;pthread_create(&writeThread, NULL, write_thread, NULL);//等待各个线程执行完成pthread_join(writeThread, NULL);for (int i = 0; i < 3; ++i) {pthread_join(readThread[i], NULL);}//销毁读写锁pthread_rwlock_destroy(&myrwlock);return 0;
}

编译生成可执行文件
gcc thread.c -o thread -lpthread
执行 ./thread 后结果如下:

------1134741248 read_thread ready
------1113761536 read_thread ready
------1103271680 write_thread ready!
------1124251392 read_thread ready
read_thread: 1124251392,x=0
read_thread: 1113761536,x=0
read_thread: 1134741248,x=0
write_thread: 1103271680,x=1
read_thread: 1134741248,x=1
read_thread: 1124251392,x=1
read_thread: 1113761536,x=1
write_thread: 1103271680,x=2
read_thread: 1124251392,x=2
read_thread: 1113761536,x=2
read_thread: 1134741248,x=2

可以看出线程 1124251392、1113761536、1134741248为读线程,每次读取 x 的值;
线程 1103271680 为写线程,每次对 x 值 + 1;写线程写完 x 后,读线程再读取 x 的值就会比上次读取的值多1。

条件变量

条件变量是一种特殊的线程同步的方式,但是与之前我们介绍的加锁保证临界资源的安全访问不同,条件变量是用来阻塞并唤醒线程的,需要与互斥锁配合才能真正达到线程同步的效果。
锁体现的是一种竞争,我离开了,通知你进来。而条件变量体现的是一种协作,我准备好了,通知你开始吧。
条件变量通常与互斥锁使用,以多线程执行业务,业务执行完后需要退出主程序的例子举例,语义可以描述为:

主线程:我已经创建了业务线程,接下来需要等待 stop = 1,stop = 1 时请唤醒我,我来继续执行后面的退出操作。
条件变量(总指挥):好的,我会一直循环观测 stop 的值,当它变为 1 时,我唤醒主线程;小弟1,请先请求互斥锁配合,获取当前stop的值,并阻塞主线程等待。
条件变量(小弟1):好的;互斥锁,请协助我,先获取到 stop 的锁,我需要获取当前的 stop 值,我将一直执行阻塞主线程操作,直到被唤醒;别担心,我拿到你给我的锁后,内部会释放这把锁,然后再阻塞主线程,因此不会因为我加了 stop 锁导致其他线程阻塞。
互斥锁:收到;现在没有其他线程要 stop 锁,这把锁可以给你。(给条件变量(小弟1)锁)。
条件变量(小弟1):主线程阻塞已完成,内部已释放 stop 锁,我将在这里等待被唤醒。条件变量(总指挥),请在需要的时候唤醒我。
条件变量(总指挥):收到,我已设置了 stop = 1 时的唤醒程序,届时将有线程唤醒条件变量(小弟1)。业务线程s,如果觉得可以退出了,请执行我预设的唤醒程序。
业务线程s:我们正在执行一些业务程序,请求已收到,请放心,我们业务线程之间会协商最终的业务完毕时间,并由指定的某个业务线程执行你预设的唤醒程序。

上面用一大段对话模拟了条件变量和互斥锁的配合,看起来写了很多内容,其实代码很简单,先贴出来压压惊:

pthread_mutex_t stop_lock;
pthread_cond_t stop_cond;
unsigned stop;
void wait_stop() 
{pthread_mutex_lock(&stop_lock); // 加锁while (stop == 0) { // 循环的作用后续文章介绍pthread_cond_wait(&stop_cond, &stop_lock); // 阻塞调用 wait_stop 的线程,等待被 pthread_cond_signal 唤醒}stop= stop-1;pthread_mutex_unlock(&stop_lock);
}void signal_stop() 
{pthread_mutex_lock(&stop_lock); if (stop == 0) {pthread_cond_signal(&stop_cond);  // 某线程调用 signal_stop ,唤醒线程}stop = 1; // 设置 stop 标志pthread_mutex_unlock(&stop_lock);
}

关于条件变量的详细使用将在下篇文章介绍,请及时关注。

相关内容

热门资讯

龙卷风袭击巴西南部 已致5死4... 当地时间11月8日,巴西地方政府通报,该国南部巴拉那州遭龙卷风袭击,导致至少5人死亡、超过430人受...
官方声明:王硕威同志不是福建舰... 11月8日,中国舰船研究设计中心官方微信公众号发布“声明”: 一、王硕威同志不是福建舰总设计师,也不...
独家 | 魏正勤调任大润发华东... 《商业观察家》获悉,高鑫零售(大润发+欧尚)CEO沈辉11月3日发布一份人事调整令,宣布自2025年...
森马服饰新增质押6650万股股... 新京报贝壳财经讯 11月8日,森马服饰发布公告,大股东邱坚强将其持有的森马服饰6650万股股份进行质...
为了下个十年,蚂蚁集团拼了! ... 本文来源:时代周报 作者:黄宇昆蚂蚁集团迎来关键组织架构升级。11月7日,蚂蚁集团CEO韩歆毅发布全...
“银发科技”扎堆亮相:地板防滑... 红星资本局11月8日消息 第八届进博会上,“银发经济”成为多家展商瞄准的方向。据联合国发布的《世界人...
老将“超期服役”卸任,2267... 作者 | 谢美浴编辑 | 付影来源 | 独角金融苏农银行(603323.SH)迎来重要人事“换血”。...
聚焦进博|“网红菠萝”跨越一万... “以前贝宁的农民只能把菠萝卖到邻国,而在进博会签下订单,能直接让农民收入翻番。”10月1日,当游客度...
燕翔:相似的PPI、不同的RO... 燕翔系方正证券首席经济学家、中国首席经济学家论坛理事核心结论国内PPI自2022年10月进入负增区间...
重温美好回忆:斯洛特谈阿诺德重... 在即将到来的欧冠联赛中,利物浦将迎战皇家马德里,而这场比赛的焦点之一无疑是阿诺德的回归。作为曾经的副...