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

线程间通信(互斥锁,死锁,信号量)

线程间的通信即多个线程间传递信息。

1.线程间通信方式

        线程间通信采取全局变量的方式,全局变量存放在数据区中,进程是操作系统资源分配的最小单元,每个进程空间独立,一个进程里所有线程共享数据段。

                                        

需要注意的是:多线程同时操作共享空间会引发资源竞争,需要加上互斥锁解决资源竞争问题。

2.互斥锁(无法同步)

        解决资源竞争的一种方式,可以看成是一种资源只能加锁一次,加锁期间不能再次加锁,也不能强制占有一个已经加锁的锁资源,必须等待锁 资源释放,也就是解锁后才能继续操作该锁,加锁和解锁中间的代码称为临界代码,也称为临界区,互斥锁只能防止多个线程对资源的竞争,不能决定代码的先后执行顺序。

                        

2.1互斥锁使用方式

        1. 定义互斥锁(全局变量) 2. 对锁初始化 3. 操作全局资源前先加锁 4. 如果加锁成功则完成对全局资源操作 5. 如果加锁失败则表示有人占用资源,必须等待其余人释放锁资源才能加锁成功 6. 直到加锁成功使用该全局资源。

2.2互斥锁的函数接口

2.2.1pthread_mutex_init初始化互斥锁

原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const 
pthread_mutexattr_t *restrict attr);
功能:
初始化互斥锁
参数:mutex:互斥锁空间首地址
attr:属性,默认为NULL
返回值:
成功返回0 
失败返回-1

2.2.2pthread_mutex_lock互斥锁加锁

原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
互斥锁加锁

2.2.3pthread_mutex_unlock互斥锁解锁

原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
互斥锁解锁

2.2.4pthread_mutex_destroy互斥锁销毁

原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
互斥锁销毁

下来使用一下互斥锁:

        互斥锁只能避免资源争抢,不能指定某个线程使用,由于没有延时,哪个线程能抢到锁取决于系统调度。

        先不使用互斥锁,不给进程上锁,会出现资源争抢的情况,进程1运行到num1 = value时进程可能会切换到进程2执行,出现了num1不等于num2的情况。

#include "head.h"
int num1 = 0;
int num2 = 0;
int value = 0;
pthread_mutex_t lock;
void *thread1(void *arg)
{while (1){//pthread_mutex_lock(&lock);//上锁num1 = value;num2 = value;value++;//pthread_mutex_unlock(&lock);//解锁}return NULL;
}
void *thread2(void *arg)
{while (1){//pthread_mutex_lock(&lock);//上锁if(num1 != num2){printf("num1 = %d num2 = %d\n", num1,num2);}//pthread_mutex_unlock(&lock);//解锁}return NULL;
}
int main(void)
{pthread_t tid1;pthread_t tid2;pthread_mutex_init(&lock, NULL);//初始化互斥锁pthread_create(&tid1, NULL, thread1, NULL);pthread_create(&tid2, NULL, thread2, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&lock);//销毁互斥锁return 0;
}

运行后会出现下面的情况

不难发现,在没有上锁的时候出现了num1=num2的情况,并且它还被打印了出来:

        这是由于num1和num2的赋值操作不是原子的,num1 = value和num2 = value看似两个独立操作,但在多线程环境下由于cpu指令重排和线程调度,他们可能会被拆分成多个机器指令,导致thread2观察到的状态出现错误,即可能会出现num1 !=num2的情况,也会出现thread2观察到num1 !=num2,但实际二者相等的情况。

再试试上锁,上锁完发现不会出现num1不等于num2的情况了。

#include "head.h"
int num1 = 0;
int num2 = 0;
int value = 0;
pthread_mutex_t lock;
void *thread1(void *arg)
{while (1){pthread_mutex_lock(&lock);//上锁num1 = value;num2 = value;value++;pthread_mutex_unlock(&lock);//解锁}return NULL;
}
void *thread2(void *arg)
{while (1){pthread_mutex_lock(&lock);//上锁if(num1 != num2){printf("num1 = %d num2 = %d\n", num1,num2);}pthread_mutex_unlock(&lock);//解锁}return NULL;
}
int main(void)
{pthread_t tid1;pthread_t tid2;pthread_mutex_init(&lock, NULL);//初始化互斥锁pthread_create(&tid1, NULL, thread1, NULL);pthread_create(&tid2, NULL, thread2, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_mutex_destroy(&lock);//销毁互斥锁return 0;
}

3.死锁

        多线程由于加锁解锁错误导致程序无法继续向下运行的状态称为死锁状态,简称为死锁。

        死锁产生的四个必要条件:

必要条件解释如何破坏
互斥资源独占使用共享锁(如读写锁)
不可剥夺资源不能强行抢走允许系统强制回收
请求保持占用资源的同时申请新资源一次性申请所有资源
循环等待线程间形成等待环

按固定顺序加锁

        只要破坏一个条件即可避免死锁!

        避免死锁,应该使加锁顺序保持一致或使用pthread_mutex_trylock()非阻塞,立即返回替换pthread_mutex_lock(阻塞,直到获取锁)。

4.信号量(可以同步)

        信号量是一种资源,只能完成四种操作:初始化、销毁、申请、释放,如果信号量资源数为0,申请资源会阻塞等待,直到占用资源的任务释放资源,资源数不为0时,才能申请到资源并继续向下执行,释放资源不会阻塞。它可以做到互斥锁不能做到的同步功能。

​​​​​​​4.1信号量的函数接口

4.1.1sem_init初始化信号量

原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
初始化信号量
参数:
sem:信号的空间的首地址
pshared:0:一个进程的多个线程间共享
非0:多个进程间共享
value:
初始化的资源的值
返回值:
成功返回0 
失败返回-1

4.1.2sem_destroy销毁信号量

原型:int sem_destroy(sem_t *sem);
功能:销毁信号量
参数:sem:信号量空间的首地址
返回值:成功返回0 失败返回-1 

4.1.3sem_wait申请信号量

        申请信号量会让信号量资源数-1, 如果信号量资源数为0,则会阻塞等待,直到有任务释放资源,才能拿到资源并继续向下 执行。

原型:int sem_wait(sem_t *sem);
功能:申请信号量
参数:sem:信号量空间首地址
返回值:成功返回0 失败返回-1 

4.1.4sem_post释放信号量

原型:int sem_post(sem_t *sem);
功能:释放信号量
参数:sem:信号量空间首地址
返回值:成功返回0 失败返回-1

应用示例:建立三个线程,将ABC依次打印出来。

#include "head.h"
sem_t sem_PA;
sem_t sem_PB;
sem_t sem_PC;
void *thread1(void *arg)
{while (1){sem_wait(&sem_PA);printf("A");sem_post(&sem_PB);}return NULL;
}
void *thread2(void *arg)
{while (1){sem_wait(&sem_PB);printf("B");sem_post(&sem_PC);}return NULL;
}
void *thread3(void *arg)
{while (1){sem_wait(&sem_PC);printf("C");sem_post(&sem_PA);}return NULL;
}
int main(void)
{sem_init(&sem_PA, 0, 1);sem_init(&sem_PB, 0, 0);sem_init(&sem_PC, 0, 0);pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(&tid1, NULL, thread1, NULL);pthread_create(&tid2, NULL, thread2, NULL);pthread_create(&tid2, NULL, thread3, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);sem_destroy(&sem_PA);sem_destroy(&sem_PB);sem_destroy(&sem_PC);return 0;
}

        ​​​​​​​                

                            显然,既没有出现资源抢夺现象,也完成了对线程的同步。

http://www.dtcms.com/a/337510.html

相关文章:

  • 148-基于Python的2024物流年度销售收入数据可视化分析系统
  • PYTHON让繁琐的工作自动化-函数
  • 功能测试相关问题
  • 使用空模型实例调用辅助函数,确定在量化过程中哪些层会被跳过(43)
  • 实现make/makefile
  • Android RxBinding 使用指南:响应式UI编程利器
  • AI智能的“进化史”:从弱人工智能到通用人工智能的跨越
  • Linux中基于Centos7使用lamp架构搭建个人论坛(wordpress)
  • [Oracle数据库] Oracle 进阶应用
  • 【完整源码+数据集+部署教程】织物缺陷检测系统源码和数据集:改进yolo11-RevCol
  • 51单片机-驱动74HC595芯片实现IO口扩展模块教程
  • C++STL之list详解
  • MySQL 运算符详解:逻辑、位运算与正则表达式应用
  • CSS:水平垂直居中
  • 蔬菜批发小程序:生产商的数字化转型利器——仙盟创梦IDE
  • 吴恩达 Machine Learning(Class 1)
  • Fluss:颠覆Kafka的面向分析的实时流存储
  • 深入解析Kafka消费者重平衡机制与性能优化实践指南
  • 【Java基础】反射,注解,异常,Java8新特性,object类-详细介绍
  • 民俗博物馆如何选择数字技术?交互体验如何创新文化传播方式?
  • Effective C++ 条款48:认识模板元编程
  • 高并发网络编程实战:深入理解epoll客户端的事件驱动模型
  • Python字典dict的初始化方法
  • 亚马逊合规风暴升级:三类账号风险预警与防御体系构建
  • 图论Day5学习心得
  • 晨控CK-GW08S与欧姆龙PLC配置Ethernet/IP通讯连接手册
  • CAN总线的安全性
  • fit函数
  • 我们为什么需要时序数据库?
  • Image and Video Tokenization with Binary Spherical Quantization 论文阅读