[Linux系统编程] 线程同步(互斥锁,读写锁,死锁,条件变量,信号量)

news2025/1/13 10:32:14

一.线程同步概念

线程同步:
协同步调,对公共区域数据按序互斥访问。防止数据混乱,产生与时间有关的错误。
数据混乱的原因:
1.资源共享(独享资源则不会)
2.调度随机(意味着数据访问会出现竞争)—线程间竞争
3.线程间缺乏必要同步机制,针对此点,如果引入某种措施,能够使得线程间按照某种规则访问公共区,就实现了线程同步

二.互斥锁(互斥量)

锁的使用:
建议锁,本身不具备强制性。 对公共数据进行保护。所有线程应该在访问公共数据前先上锁再访问。

如下图,T1 T2都使用锁,那么在访问此区域前,若T1上锁,T2就会阻塞在锁上,但T3不使用锁,也可以直接访问此区域,说明锁不是强制的,只是实现线程同步的一种手段,一种建议锁。
在这里插入图片描述

互斥锁主要应用函数:
pthread_mutex_init 函数
pthread_mutex_destory 函数
pthread_mutex_lock 函数
pthread_mutex_trylock 函数
pthread_mutex_unlock 函数

以上5个函数的返回值都是:成功返回0,失败返回错误号

pthread_mutex_t 类型,其本质是一个结构体。为简化理解,可忽略其实现细节,简单当成整数看待,只有两种取值:0,1 -------1代表未上锁,0代表已上锁。

使用mutex(互斥量、互斥锁)一般步骤:

  1. pthread_mutex_t lock; 创建锁

  2. pthread_mutex_init; 初始化 1

  3. pthread_mutex_lock;加锁 1-- --> 0

  4. 访问共享数据(stdout)

  5. pthrad_mutext_unlock();解锁 0++ --> 1

  6. pthead_mutex_destroy;销毁锁

初始化的两种方式:
动态初始化:
pthread_mutex_init(&mutex, NULL);
静态初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

在这里插入图片描述利用pthread_mutex_t 互斥锁,实现线程间同步例子

1.#include <stdio.h>  
2.#include <string.h>  
3.#include <pthread.h>  
4.#include <stdlib.h>  
5.#include <unistd.h>  
6.  
7.pthread_mutex_t mutex;      // 全局区定义一把互斥锁  
7.  
9.void *tfn(void *arg)  
10.{  
8.    srand(time(NULL));  
9.  
10.    while (1) {  
11.        pthread_mutex_lock(&mutex);     // 加锁  
12.        printf("hello ");  
13.        sleep(rand() % 3);  // 模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误  
14.        printf("world\n");  
15.        pthread_mutex_unlock(&mutex);   // 解锁  
16.        sleep(rand() % 3);  
17.    }  
18.  
19.    return NULL;  
23.}  
20.  
25.int main(void)  
26.{  
21.    pthread_t tid;  
22.    srand(time(NULL));  
23.    int ret = pthread_mutex_init(&mutex, NULL);    // 在创建线程前初始化互斥锁  
24.    
25.    if(ret != 0){  
26.        fprintf(stderr, "mutex init error:%s\n", strerror(ret));  
27.        exit(1);  
28.    }  
29.  
30.    pthread_create(&tid, NULL, tfn, NULL);  
31.    while (1) {  
32.        pthread_mutex_lock(&mutex);     // 加锁  
33.        printf("HELLO ");  
34.        sleep(rand() % 3);  
35.        printf("WORLD\n");  
36.        pthread_mutex_unlock(&mutex);   // 解锁  
37.        sleep(rand() % 3);  
38.    }  
39.    pthread_join(tid, NULL);  
40.      
41.    pthread_mutex_destory(&mutex);     // 销毁互斥锁  
42.  
43.    return 0;  
49.}  

编译运行,结果如下:
在这里插入图片描述可以看到,主线程和子线程在访问共享区时就没有交叉输出的情况了。

互斥锁使用技巧:

尽量保证锁的粒度, 越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)

互斥锁,本质是结构体。 我们可以看成整数。 初值为 1。(pthread_mutex_init() 函数调用成功。)
加锁: --操作, 其它线程在pthread_mutex_lock()这个函数上。
解锁: ++操作, 唤醒阻塞在锁上的线程。
try锁:不阻塞的上锁函数pthread_mutex_trylock,它表示尝试加锁,成功直接上锁,失败则直接返回错误号 EBUSY

三.读写锁

在这里插入图片描述读写锁:

锁只有一把。但上锁方式不同:
读方式给数据加锁——读锁。
以写方式给数据加锁——写锁。

读共享,写独占。
写锁优先级高。

当一个线程上写锁时,无论其他线程以何种方式上锁,都会被阻塞。
当上读锁时,其他线程若以读锁上锁时,可以共享读锁,不会被阻塞。
此即读时共享。
另外,当多个线程同时到达,想要上锁时,优先分配给写锁。
相较于互斥量而言,当读线程多的时候,可以提高访问效率

读写锁操作函数
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_rdlock(&rwlock); 上读锁
pthread_rwlock_wrlock(&rwlock); 上写锁
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);

以上函数都是成功返回0,失败返回错误号。
pthread_rwlock_t 类型 用于定义一个读写锁变量
pthread_rwlock_t rwlock

四.死锁的产生

死锁:
是使用锁不恰当导致的现象:
1. 对一个锁反复lock。
2. 两个线程,各自持有一把锁,请求另一把。(线程本身持有资源,缺少另外的资源执行,而另外的线程持有它所需的资源,呈环状等待。本身又不会主动释放资源,所以一直阻塞)

在这里插入图片描述

五.静态初始化条件变量和互斥量

条件变量本身不是锁! 但是通常结合互斥锁mutex来使用。

条件变量结构体类型: pthread_cond_t cond;

初始化条件变量:

  1. pthread_cond_init(&cond, NULL); 动态初始化。
  2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 静态初始化。(与初始化互斥锁类似)

相关操作函数:
pthread_cond_wait(&cond, &mutex);
此函数作用比较复杂,一旦被调用,就会先阻塞,再释放已经持有的锁,直到该函数被其他线程唤醒后再次上锁。常用在pthread_mutex_lock上锁之后。

作用:
1) 阻塞等待
2) 解锁已经加锁成功的互斥量 (pthread_mutex_unlock(&mutex)),12两步为一个原子操作
3) 当条件满足(被使用pthread_cond_wait唤醒时),函数返回,解除阻塞并重新申请获取互斥锁。 (相当于, pthread_mutex_lock(&mutex);)

原本线程的调度是随机抢占的,任何一个线程抢到锁后都会先执行。

而利用条件变量,即使某线程率先抢到了锁,若条件不满足,也不能向下执行,需要先放锁给其他线程使用,提供了一种控制线程的手段。下文的生产者消费者模型就是这种情况的一种。
在这里插入图片描述pthread_cond_signal(): 唤醒阻塞在条件变量上的 (至少)一个线程。

pthread_cond_broadcast(): 唤醒阻塞在条件变量上的 所有线程。

六.条件变量的生产者消费者模型分析

在这里插入图片描述生产者线程:不断生产数据
消费者线程:不断消费数据(条件:共享区内有数据)

代码如下(多个消费者,一个生产者):

1.#include <stdio.h>  
2.#include <stdlib.h>  
3.#include <string.h>  
4.#include <unistd.h>  
5.#include <errno.h>  
6.#include <pthread.h>  
7.  
8.void err_thread(int ret, char *str)  
9.{  
8.    if (ret != 0) {  
9.        fprintf(stderr, "%s:%s\n", str, strerror(ret));  
10.        pthread_exit(NULL);  
11.    }  
14.}  
12.  
16.struct msg {  
13.    int num;  
14.    struct msg *next;  
19.};  
15.  
21.struct msg *head;  
16.  
23.pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定义/初始化一个互斥量  
24.pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量  
17.  
26.void *produser(void *arg)  
27.{  
18.    while (1) {  
19.        struct msg *mp = malloc(sizeof(struct msg));  
20.  
21.        mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`  
22.        printf("--produce %d\n", mp->num);  
23.  
24.        pthread_mutex_lock(&mutex);                         // 加锁 互斥量  
25.        mp->next = head;                                    // 写公共区域  
26.        head = mp;  
27.        pthread_mutex_unlock(&mutex);                       // 解锁 互斥量  
28.  
29.        pthread_cond_signal(&has_data); // 唤醒阻塞在条件变量 has_data上的线程.  
30.  
31.        sleep(rand() % 3);  
32.    }  
33.  
34.    return NULL;  
45.}  
35.  
47.void *consumer(void *arg)  
48.{  
36.    while (1) {  
37.        struct msg *mp;  
38.  
39.        pthread_mutex_lock(&mutex);                         // 加锁 互斥量  
41.        while(head == NULL) {    
42.            pthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁  
43.        }                                                   // pthread_cond_wait 返回时, 重写加锁 mutex  
44.  
45.        mp = head;  
46.        head = mp->next;  
47.  
48.        pthread_mutex_unlock(&mutex);                       // 解锁 互斥量  
49.        printf("---------consumer id = %lu  :%d\n", pthread_self(), mp->num);  
50.  
51.        free(mp);  
52.        sleep(rand()%3);  
53.    }  
54.  
55.    return NULL;  
68.}  
56.  
70.int main(int argc, char *argv[])  
71.{  
57.    int ret;  
58.    pthread_t pid, cid;  
59.  
60.    srand(time(NULL));  
61.  
62.    ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者  
63.    if (ret != 0)   
64.        err_thread(ret, "pthread_create produser error");  
65.  
66.    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  
67.    if (ret != 0)   
68.        err_thread(ret, "pthread_create consuer error");  
69.      
70.    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  
71.    if (ret != 0)   
72.        err_thread(ret, "pthread_create consuer error");  
73.      
74.    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者  
75.    if (ret != 0)   
76.        err_thread(ret, "pthread_create consuer error");  
77.  
78.    pthread_join(pid, NULL);  
79.    pthread_join(cid, NULL);  
80.  
81.    return 0;  
97.}  

生产者此时生产了数据后,会同时唤醒两个因条件变量阻塞的消费者,若结果A消费者拿到锁,开始消费数据,B消费者就阻塞在锁上。
A消费完数据,把锁归还,B被唤醒,然而此时已经没有数据供B消费了。所以这里有个逻辑错误,消费者阻塞在条件变量那里应该使用**while循环。**这样A消费完数据后,B做的第一件事不是去拿锁,而是判定条件变量。
在这里插入图片描述

七.信号量概念及其相关操作函数

信号量: 应用于线程、进程间同步。

相当于 初始化值为 N 的互斥量。
N值:可以同时访问共享数据区的线程数。

sem_t sem; 定义类型。
函数:
1.int sem_init(sem_t*sem, int pshared, unsigned int value);
参数:
sem: 信号量
pshared: 0: 用于线程间同步 1: 用于进程间同步
value:N值。(指定同时访问的线程数)

2.sem_destroy();
销毁sem信号量

3.sem_wait(); (对比 pthread_mutex_lock)
一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 - - 就会阻塞。

4.sem_post(); (对比 pthread_mutex_unlock)
一次调用,做一次++ 操作. 当信号量的值为 N 时, 再次 ++ 就会阻塞。

互斥量即N值为1的信号量。

八.信号量实现的生产者消费者模型

在这里插入图片描述共享区:固定数量的数据区,如int[N]数组
生产者:不断生产,在产品数量为5时阻塞(sam_post()),不再生产。
消费者:不断消费,在产品数量为0时(sam_wait())阻塞。
所以就需要两种信号量sam1,sam2,sam1表示剩余空闲格子,初值为N;sam2表示产品数量,初值为0;
生产者生产一个,sam1–,sam2++
消费者消费一个,sam1++,sam2–;

代码如下:

1./*信号量实现 生产者 消费者问题*/  
2.  
3.#include <stdlib.h>  
4.#include <unistd.h>  
5.#include <pthread.h>  
6.#include <stdio.h>  
7.#include <semaphore.h>  
8.  
9.#define NUM 5                 
10.  
11.int queue[NUM];                                     //全局数组实现环形队列  
12.sem_t blank_number, product_number;                 //空格子信号量, 产品信号量  
13.  
14.void *producer(void *arg)  
15.{  
16.    int i = 0;  
17.  
18.    while (1) {  
19.        sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待  
20.        queue[i] = rand() % 1000 + 1;               //生产一个产品  
21.        printf("----Produce---%d\n", queue[i]);          
22.        sem_post(&product_number);                  //将产品数++  
23.  
24.        i = (i+1) % NUM;                            //借助下标实现环形  
25.        sleep(rand()%1);  
26.    }  
27.}  
28.  
29.void *consumer(void *arg)  
30.{  
31.    int i = 0;  
32.  
33.    while (1) {  
34.        sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待  
35.        printf("-Consume---%d\n", queue[i]);  
36.        queue[i] = 0;                               //消费一个产品   
37.        sem_post(&blank_number);                    //消费掉以后,将空格子数++  
38.  
39.        i = (i+1) % NUM;  
40.        sleep(rand()%3);  
41.    }  
42.}  
43.  
44.int main(int argc, char *argv[])  
45.{  
46.    pthread_t pid, cid;  
47.  
48.    sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享 -- 0  
49.    sem_init(&product_number, 0, 0);                //产品数为0  
50.  
51.    pthread_create(&pid, NULL, producer, NULL);  
52.    pthread_create(&cid, NULL, consumer, NULL);  
53.  
54.    pthread_join(pid, NULL);  
55.    pthread_join(cid, NULL);  
56.  
57.    sem_destroy(&blank_number);  
58.    sem_destroy(&product_number);  
59.  
60.    return 0;  
61.}  

编译运行,结果如下:在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2215191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一篇闪击常用放大器电路(学习笔记)

文章目录 声明概念名词经典电路分析反向放大器同向放大器加法器减法器积分电路微分电路差分放大电路电流->电压转换电路电压->电流转换电路 虚短与虚断一、虚短二、虚断 一些碎碎念 声明 ​ 本文是主要基于以下两篇博客所做的笔记&#xff1a; 模电四&#xff1a;基本放…

图论day60|108.冗余连接(卡码网) 、109.冗余连接II(卡码网)【并查集 摧毁信心的一题,胆小的走开!】

图论day60|108.冗余连接&#xff08;卡码网&#xff09;、109.冗余连接II&#xff08;卡码网&#xff09;【并查集 摧毁信心的一题&#xff0c;胆小的走开&#xff01;】 108.冗余连接&#xff08;卡码网&#xff09;109.冗余连接II&#xff08;卡码网&#xff09;【并查集 摧毁…

Flythings学习(四)串口通信

文章目录 1 串口编程基本步骤1.1 打开串口1.2 配置串口 1.3 读串口1.4 发送串口1.5 关闭串口 2 综合使用3 如何在软件上保证串口稳定通信4 flythings中的串口通讯5 协议接收部分使用和修改方法6 通讯协议数据怎么和UI控件对接 1 串口编程基本步骤 串口通信有5个步骤 1.打开串口…

【干货】Ftrans跨网数据摆渡系统,如何实现网间数据安全高效流转?

随着企业数字化转型的逐步深入&#xff0c;企业投入了大量资源进行信息系统建设&#xff0c;信息化程度日益提升。在这一过程中&#xff0c;企业也越来越重视核心数据资产的保护&#xff0c;数据资产的安全防护成为企业面临的重大挑战。 一、网络隔离的必要性 绝大多数企业为…

步进电机定时器与编码器定时器关系

速度环PID比较计数器的增量公式&#xff1a; 位置环PID比较计数器的增量公式&#xff1a;

【 香格里拉酒店-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

gitLab配置ssh

1打开git命令行&#xff0c;创建秘钥 ssh-keygen -t rsa -b 4096 -C "用户名xxx.com" 2执行下面的命令查看公钥 cat ~/.ssh/id_rsa.pub 3#复制公钥到gitlab网址上ssh页面添加ssh的key&#xff08;公钥&#xff09; 4本地的git命令行中添加账户邮箱 git config -…

Centos安装Nginx 非Docker

客户的机器属于 Centos7 系列&#xff0c;由于其较为陈旧&#xff0c;2024开始众多镜像和软件源都已失效。此篇文章将详细记录在 Centos7 操作系统上从零开始安装 Nginx 的整个流程。 本文Nginx是安装在/usr/local/nginx下 详细步骤如下&#xff1a; 准备Nginx安装包&#x…

ABB高性能矢量型变频器ACS380的性能优势

ABB ACS380变频器是一款可靠、易用、灵活的高性能矢量型变频器&#xff0c;其优异的电机控制、耐久的设计以及与所有主要工业自动化网络的连接性而成为各类设备电机驱动的理想之选。 1. 广泛的功率范围 ABB ACS380 变频器拥有广泛的功率输出区间&#xff0c;在单项 230V 电压下…

springboot员工管理系统-计算机毕业设计源码35173

目 录 1 绪论 1.1 研究背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系统流程…

第三方软件测评机构分享:软件性能测试方法

软件性能测试指测试人员通过各种手段检测产品是否符合性能指标、评估系统服务能力和完成系统优化的测试活动&#xff0c;专业第三方软件测评机构卓码软件测评简要分享以下几种性能测试方法&#xff1a; 1.压力测试   通过对软件系统不断施加压力&#xff0c;识别系统性能拐…

07 django管理系统 - 部门管理 - 搜索部门

在dept_list.html中&#xff0c;添加搜索框 <div class"container-fluid"><div style"margin-bottom: 10px" class"clearfix"><div class"panel panel-default"><!-- Default panel contents --><div clas…

工程文件参考——STM32+HAL+SPI主从机通讯

文章目录 前言CubeMX设置SPI设置NSS设置 SPI从机代码SPI主机代码 前言 关于如何简单的写一个稳定的SPI主从机通讯&#xff0c;思路很简单 1、SPI高速传输的时候很容易出现错位之类的问题&#xff0c;CRC的校验首先是必要的。在STM32中SPI使用DMA通讯可以自动执行CRC的校验&…

路由器原理和静态路由配置

一、路由器的工作原理 根据路由表转发数据 接收数据包→查看目的地址→与路由表进行匹配找到转发端口→转发到该端口 二、路由表的形成 它是路由器中维护的路由条目的集合&#xff0c;路由器根据路由表做路径选择&#xff0c;里面记录了网段ip地址和对应下一跳接口的接口号。…

人工智能实训室建设的必要性

在当今科技迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;已不再局限于科幻电影的虚构情节&#xff0c;而是作为一股颠覆性力量&#xff0c;深刻影响着全球经济、社会乃至文化的发展。随着《新一代人工智能发展规划》等政策的出台&#xff0c;中国已明确将人工…

RA6M5——GPIO

文章目录 GPIO输入输出RASC图形化配置输出模式&#xff1a;输入模式&#xff1a;配置选项&#xff1a; 接口函数实例代码&#xff1a; GPIO输入输出 RASC图形化配置 输出模式&#xff1a; 输入模式&#xff1a; 配置选项&#xff1a; 配置项取值/描述Model “Input mode”&a…

疾病防控|基于springBoot的疾病防控综合系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何…

离线安装bitnami-gitlab8.8.4+汉化

注意&#xff1a; 常规安装gitlab需要联网&#xff0c;而按装bitnami-gitlab无需联网(bitnami-gitlab用于内网环境无法联网时安装gitlab&#xff0c;两者是一个东西只是名字不一样)bitnami-gitlab-8.8.4版本可以汉化成功新用户注册账户无需激活也可以直接登录&#xff0c;因为…

thingsboard3.8的运行使用——设备管理与遥感数据测试

1、首先增加设备配置&#xff0c;如下&#xff1a; 建立一个定位器的设备&#xff0c;临时先用默认规则链&#xff0c;不过正常应该建立单独的定位器规则链 传输配置如下&#xff1a; 使用MQTT协议&#xff0c;相应的topic采用默认的方式 这里告警先不设置&#xff0c;后续到…