当前位置: 首页 > news >正文

线程同步学习

概念

有A、B、C三个线程,A线程负责输入数据,B线程负责处理数据、C线程负责输出数据,这三个线程之间就存在着同步关系,即A必须先执行,B次之,C最后执行,否则不能得到正确的结果。
那么所谓线程同步,是指多个线程在发生的事件存在着某种时序关系,它们必须按规定时间执行,以共同完成一项任务。多个线程在访问共享资源时,通过一定的机制来协调它们的执行顺序,以确保共享资源在任何时刻都能被正确地访问和修改。

为什么需要线程同步呢?
在多线程编程中,不同的线程可能会同时访问和修改同一个共享资源。如果没有适当的同步机制,可能会导致数据不一致、资源竞争等问题。例如,两个线程同时对一个计数器进行递增操作,如果不进行同步,可能会导致计数器的值不正确。

还记得上一篇博客中讲过的一个知识点就是线程的竞争能力会有不同,竞争能力强的线程可能从头到尾都占用一个锁,所以线程同步是来解决这种情况的。实现线程同步的方法主要有三种,分别是互斥锁、信号量和条件变量。本博客主要是讲解使用条件变量的方法来实现线程同步。

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中,这种情况就需要用到条件变量。条件变量通常与互斥锁一起使用,用于线程之间的等待和通知。
当一个线程需要等待某个条件满足时,它可以使用条件变量进行等待。在等待之前,线程必须先获取互斥锁,以确保对共享资源的访问是安全的。
条件变量的函数接口跟互斥锁的基本一样,定义方式也一样,可以局部或者全局定义。它存在于<pthread.h>头文件中,类型是pthread_cond_t
全局定义
函数原型:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
变量名可以自己定义,全局的条件变量不需要手动销毁。


局部定义
条件变量的局部定义需要自己手动创建和销毁,这些都需要函数接口来实现。我们接下来就来学习这些接口。
pthread_cond_init
功能:用于初始化一个条件变量。
函数原型:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数:

  • cond:要初始化的条件变量,类型是pthread_cond_t *的指针。
  • attr:条件变量的属性,一般设置为nullptr即可。

返回值:成功返回0,失败返回错误码。


pthread_cond_destory
功能:用于销毁一个条件变量。
函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
返回值:成功返回0,失败返回错误码。


pthread_cond_wait
功能:用于让一个线程进入等待队列等待,直到被唤醒。
函数原型:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:

  • cond:指向条件变量的指针。
  • mutex:指向互斥锁的指针。在调用pthread_cond_wait之前,线程必须已经持有这个互斥锁。传入锁的指针是为了帮助这个线程释放该锁,从而让其他线程也能够轻松申请锁进入等待队列等待。

返回值:成功返回0,失败返回错误码。


进入到等待队列之后,可以有2种方式唤醒队列中的线程。分别是唤醒一个线程和唤醒所有线程
pthread_cond_signal
功能:唤醒等待队列中的第一个线程。
函数原型:int pthread_cond_signal(pthread_cond_t *cond);
返回值:成功返回0,失败返回错误码。

pthread_cond_broadcast
功能:唤醒等待队列中的所有线程。
函数原型:int pthread_cond_broadcast(pthread_cond_t *cond);


接下来,我将用图片的方式来帮助大家理解条件变量函数接口的使用方法。
如图所示,有三个线程和一个临界资源,现在线程都要去争夺临界资源。
请添加图片描述

线程1先申请到了锁,但是我们给临界资源添加了条件变量,所以线程1不能直接进入到临界资源,而是要先到等待队列当中去,并且要释放锁。
请添加图片描述

线程2和线程3也是一样,都需要先申请锁,然后发现我们给临界资源添加了条件变量,所以要先去等待队列当中去,并且释放锁。
请添加图片描述

现在所有的线程都进入到队列当中了,先加入到等待队列是为了保持线程同步。也就是谁先访问的临界资源,最后谁就第一个访问。还记得上一篇博客中举的VIP自习室的例子吗
请添加图片描述

绿色代表的是已经在等待队列当中的线程了,红色代表的是新来的线程,他要先到自习室的门口,发现自习室里面有人,于是他需要排到队列的尾部去,直到轮到他进入到临界资源当中。而自习室当中的人要是临时有事,需要离开,那么他要先把钥匙归还并且还需要摇响铃铛,告诉下一个人该到他自习了,当解决完事情之后,需要重新排队才行。
如果使用pthread_cond_signal接口唤醒队列的第一个线程,那么线程就可以重新获得之前的锁,进入到临界资源当中去。
请添加图片描述

假设线程1执行完临界区的代码之后,又重新申请了锁,由于条件变量的存在,线程1不能直接访问临界资源,必须要到等待队列的尾部才行。这样就能避免一个线程一直访问临界资源。
请添加图片描述

如果使用pthread_cond_broadcast接口唤醒所有的线程,那么就会让所有的线程再次竞争同一把锁,谁先竞争到谁就访问临界资源,当访问完毕后,剩下的线程继续竞争,再访问临界资源。
现在通过代码来理解这些接口有什么作用。

int cnt = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void *Count(void *args)
{pthread_detach(pthread_self());uint64_t number = (uint64_t)args;cout << "pthread: " << number << "create sucess" << endl;while (true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);cout << "pthread: " << number << ", cnt: " << cnt++ << endl;pthread_mutex_unlock(&mutex);}
}int main()
{for (uint32_t i = 0; i < 5; i++){pthread_t tid;pthread_create(&tid, nullptr, Count, (void *)&i);usleep(1000);}sleep(3);cout << "main thread ctrl begin: " << endl;while (1){sleep(1);pthread_cond_signal(&cond);// pthread_cond_broadcast(&cond);cout << "signal one thread..." << endl;}return 0;
}

先创建一个全局变量cnt用来观察每个线程的变化,然后再全局初始化mutexcond。在主函数当中,创建了5个线程,休眠3秒后,接着在循环当中每过一秒使用signal接口唤醒队列当中的每一个线程,而线程执行的函数Count,进入while循环后,通过pthread_mutex_lock加锁,试图访问临界资源。但是我们使用了条件变量进行限制,需要先通过pthread_cont_wait进入等待队列,并且释放掉申请的锁,唤醒线程则是到主线程当中的pthread_cond_signal接口来唤醒,每过一秒钟唤醒一次。
然后我们来看代码结果:
请添加图片描述

这分别是用2个唤醒接口运行出来的结果,我们可以看到signal接口,每秒钟唤醒一个接口,5个线程都唤醒后,又重新开始;而broadcast接口,一次唤醒所有的线程,也可以看到第一次时3号线程的竞争能力强,第二次时0号线程的竞争能力强,但可以确定的是,每个轮次中,每个线程都获得了一次临界资源,这也算是线程同步。

http://www.xdnf.cn/news/504919.html

相关文章:

  • 8天Python从入门到精通【itheima】-11~13
  • SpringBootAdmin:全方位监控与管理SpringBoot应用
  • nt!MiInitializePfn函数分析之nt!MiPfPutPagesInTransition函数的关键一步
  • Golang 范型
  • 编程日志5.10
  • QLoRA: Efficient Finetuning of Quantized LLMs
  • acwing5579 增加模数
  • 深入了解 VPC 端点类型 – 网关与接口
  • Stacking(堆叠):集成学习中的“超级英雄团队”
  • STM32+ESP8266连接onenet新平台
  • 【嵌入式DIY实例-Arduino篇】-OLED实现乒乓游戏
  • Seata源码—5.全局事务的创建与返回处理二
  • nodejs特性解读
  • 小刚说C语言刷题—1230蝴蝶结
  • 业务系统上线为什么这么难
  • 【Unity 2023 新版InputSystem系统】新版InputSystem 如何进行项目配置并安装
  • 【RocketMQ Broker 相关源码】- 清除不活跃的 broker
  • JavaScript【6】事件
  • windows 11安装Python3.9、mujoco200、mujoco_py2.0.2.8、metaworld
  • 51单片机仿真突然出问题
  • 如何在 Windows 11 或 10 的 CMD 中检查固件
  • 元件伏安特性及基尔霍夫定理的相量形式
  • 【as 在长难句中有哪几种翻译?】
  • 北京市工程技术人才职称评价基本标准条件解读
  • PLC和变频器之间如何接线
  • 2020CCPC河南省赛题解
  • V型不锈钢对夹球阀:高性价比工业控制解决方案-耀圣
  • 项目复习(2)
  • 黑客帝国电子表html
  • java中的包机制