linux学习:线程安全(信号量+互斥锁读写锁+条件变量+可重入函数)

news2024/12/24 8:21:06

目录

信号量

有名信号量

步骤

 api

创建、打开一个POSIX有名信号量

 对 POSIX 有名信号量进行 P、V 操作

关闭、删除 POSIX 有名信号量

例子

无名信号量

步骤

api

初始化、销毁 POSIX 无名信号量

互斥锁读写锁

例子

两条线程 使用互斥锁来互斥地访问标准输出

在加锁的时候可以选择加读或者写 锁

条件变量

api

初始化、销毁条件变量

进入条件变量等待队列,同时对获取配套的互斥锁

唤醒全部,或者一个条件变量等待队列中的线程

例子

可重入函数

原则


信号量

信号量分为两种,分别是无名信号量和有名信号量, 这两种信号量比之前介绍的 system-V 的信号量机制要简洁,虽然没有后者的应用范围那么广泛,但是 POSIX 良好的设计使得他们更具吸引力

有名信号量

名字前面会带一个 “/”,例如 ‘/name“

这样的信号量其实是一个特殊的文件,创建成功之后将会被放置在系统的一个特殊的 虚拟文件系统/dev/shm 之中,不同的进程间只要约定好一个相同的名字,他们就可以通 过这种有名信号量来相互协调,在进程退出之后 他们并不会自动消失,而需要手工删除并释放资源

步骤

  • 使用 sem_open( )来创建或者打开一个有名信号量
  • 使用 sem_wait( )和 sem_post( )来分别进行 P 操作和 V 操作
  • 使用 sem_close( )来关闭他
  • 使用 sem_unlink( )来删除他,并释放系统资源

 api

创建、打开一个POSIX有名信号量

 对 POSIX 有名信号量进行 P、V 操作

注意

  • 每次申请和释放的资源数都是 1。其中调用 sem_wait( )在资源为 0 时会导致阻塞,如果 不想阻塞等待,可以使用 sem_trywait( )来替代
关闭、删除 POSIX 有名信号量

例子

进程 Jack 通过共享内存 SHM 给进程 Rose 发送数据,以及使用了 POSIX 有名信号量来实现两条线程间的同步

head4namedsem.h

1 #ifndef _HEAD4NAMESEM_H_
2 #define _HEAD4NAMESEM_H_
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <sys/shm.h>
8 #include <semaphore.h>
9 #include <fcntl.h>
10
11 #define PROJ_PATH "." // 用以产生共享内存的路径和整数
12 #define PROJ_ID 100
13
14 #define SHMSZ 1024 // 共享内存的大小
15 #define SEMNAME "sem4test" // 有名信号量的名字
16
17 #endif

Jack.c  向共享内存中写入数据,并通过 POSIX 有名信号通知其他进程数据的更新

1 #include "head4namedsem.h" 2
3 int main(int argc, char **argv)
4 {
5     key_t key = ftok(PROJ_PATH, PROJ_ID); // 生成一个用于共享内存和信号量的键值
6     int id = shmget(key, SHMSZ, IPC_CREAT|0666); //创建或获取一个共享内存区域,大小为 SHMSZ 字节
7     char *shmaddr = shmat(id, NULL, 0); //将共享内存连接到当前进程的地址空间中,并返回一个指向共享内存区域的指针
8
9     // 创建或者打开 POSIX 有名信号  初始值为0   SEMNAME 是信号量的名称
10     sem_t *s;
11     s = sem_open(SEMNAME, O_CREAT, 0777, 0);
12
13     // 每当向共享内存写入数据之后,就让信号量的值加 1
14     while(1)
15     {
16         fgets(shmaddr, SHMSZ, stdin);// 写入共享内存,
17         sem_post(s);// 增加信号量的值
18
19         if(!strncmp(shmaddr, "quit", 4)) // 输入 quit 退出对话
20             break;
21     }
22
23     // 关闭并且删除信号量
24     sem_close(s);
25     sem_unlink(SEMNAME);
26     return 0;
27 }

Rose.c

1 #include "head4namedsem.h" 2
3 int main(int argc, char **argv)
4 {
5     key_t key = ftok(PROJ_PATH, PROJ_ID); // 生成一个用于共享内存和信号量的键值
6     int id = shmget(key, SHMSZ, IPC_CREAT|0666); //创建或获取一个共享内存区域
7     char *shmaddr = shmat(id, NULL, 0); // 将共享内存连接到当前进程的地址空间中
8
9     // 创建或者打开 POSIX 有名信号量 初始值为0。SEMNAME 是信号量的名称
10     sem_t *s;
11     s = sem_open(SEMNAME, O_CREAT, 0777, 0);
12
13     // 当取得信号量资源时,才能访问 SHM
14     while(1)
15     {
16         sem_wait(s);//等待信号量资源
17         if(!strncmp(shmaddr, "quit", 4)) // 若对方写入 quit,则退出
18             break;
19
20         printf("from Jack: %s", shmaddr);
21     }
22    
23     // 关闭 POSIX 有名信号量
24     sem_close(s);
25     return 0
26 }

无名信号量

步骤

  • 在这些线程都能访问到的区域定义这种变量(比如全局变量),类型是 sem_t。
  • 在任何线程使用它之前,用 sem_init( )初始化他。
  • 使用 sem_wait( )/sem_trywait( )和 sem_post( )来分别进行 P、V 操作。
  • 不再需要时,使用 sem_destroy( )来销毁他

api

初始化、销毁 POSIX 无名信号量

 无名信号量一般用在进程内的线程间,因此 pshared 参数一般都为 0。当将此种信号 量用在进程间时,必须将他定义在各个进程都能访问的地方——比如共享内存之中

  • 三种信号量
    • system-V 信号量
    • POSIX 信号量(named-sem 和 unnamed-sem)
  • sys-V 信号量较古老,语法艰涩。POSIX 信号量简单,轻量。
  • sys-V 信号量可以对代表多种资源的多个信号量元素同一时间进行原子性的 P/V 操作,POSIX 信号量每次只能操作一个信号量。
  • sys-V 信号量和 named-sem 是系统范围的资源,进程消失之后继续存在,而 unnamed-sem 是进程范围的资源,随着进程的退出而消失
  • sys-V 信号量的 P/V 操作可以对信号量元素加减大于 1 的数值,而 POSIX 信号量 每次 P/V 操作都是加减 1。
  • sys-V 信号量甚至还支持撤销操作——一个进程对 sys-V 信号量进行 P/V 操作时 可以给该操作贴上需要撤销的标识,那么当进程退出之后,系统会自动撤销那些做了标识的 操作。而 POSIX 信号没有此功能
  • sys-V 信号量和 named-sem 适合用在进程间同步互斥,而 unamed-sem 适合 用在线程间同步互斥

互斥锁读写锁

如果信号量的值最多为 1,那实际上相当于一个共享资源在任意时刻最多只能有一个线 程在访问,这样的逻辑被称为“互斥”。这时,有一种更加方便和语义更加准确的工具来满 足这种逻辑,他就是互斥锁

例子

两条线程 使用互斥锁来互斥地访问标准输出

1 #include <stdio.h>
2 #include <pthread.h>
3
4 pthread_mutex_t m; // 定义一个互斥锁变量
5 // 输出字符串
6 void output(const char *string)
7 {
8     const char *p = string;
9
10     while(*p != '\0')
11     {
12         fprintf(stderr, "%c", *p);
13         usleep(100);
14         p++;
15     }
16 }
17 // 线程函数
18 void *routine(void *arg)
19 {
20     
21     pthread_mutex_lock(&m);// 锁定互斥锁
22     output("message delivered by child.\n");
23     pthread_mutex_unlock(&m); // 解锁
24
25     pthread_exit(NULL);// 退出线程
26 }
27
28 int main(int argc, char **argv)
29 {
30     // 在任何线程使用该互斥锁之前必须先初始化互斥锁
31     pthread_mutex_init(&m, NULL);
32
33     pthread_t tid;// 线程标识符
34     pthread_create(&tid, NULL, routine, NULL);// 创建线程运行routine函数
35
36     // 在访问共享资源(标准输出设备)之前,加锁
37     pthread_mutex_lock(&m);//上锁
38     output("info output from parent.\n");// 输出
39     pthread_mutex_unlock(&m); // 使用完了,记得解锁
40
41     // 阻塞等待子线程退出,然后销毁互斥锁。
42     pthread_join(tid, NULL);
43     pthread_mutex_destroy(&m);
44
45     return 0;
46 }

在加锁的时候可以选择加读或者写 锁

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5
6 static pthread_rwlock_t rwlock;//定义读写锁
7
8 int global = 0;//读写标志
9 // 线程函数
10 void *routine1(void *arg)
11 {
12     // 对共享资源进行写操作之前,必须加写锁(互斥锁)
13     pthread_rwlock_wrlock(&rwlock);
14
15     global += 1;
16     printf("I am %s, now global=%d\n", (char *)arg, global);
17
18     // 访问完之后释放该锁
19     pthread_rwlock_unlock(&rwlock);
20
21     pthread_exit(NULL);
22 }
23 // 线程函数
24 void *routine2(void *arg)
25 {
26     // 对共享资源进行写操作之前,必须加写锁(互斥锁)
27     pthread_rwlock_wrlock(&rwlock);
28
29     global = 100;
30     printf("I am %s, now global=%d\n", (char *)arg, global);
31
32     // 访问完之后释放该锁
33     pthread_rwlock_unlock(&rwlock);
34
35     pthread_exit(NULL);
36 }
37 // 线程函数
38 void *routine3(void *arg)
39 {
40     // 对共享资源进行读操作之前,可以加读锁(共享锁)
41     pthread_rwlock_rdlock(&rwlock);
42
43     printf("I am %s, now global=%d\n", (char *)arg, global);
44
45     // 访问完之后释放该锁
46     pthread_rwlock_unlock(&rwlock);
47
48     pthread_exit(NULL);
49 }
50
51 int main (int argc, char *argv[])
52 {
53
54     pthread_rwlock_init(&rwlock,NULL);// 初始化读写锁
55
56     // 创建三条线程,对共享资源同时进行读写操作,然后等待线程结束
57     pthread_t t1, t2, t3;
58     pthread_create(&t1, NULL, routine1, "thread 1");
59     pthread_create(&t2, NULL, routine2, "thread 2");
60     pthread_create(&t3, NULL, routine3, "thread 3");
61     pthread_join(t1, NULL);
62     pthread_join(t2, NULL);
63     pthread_join(t3, NULL);
64
65     // 销毁读写锁
66     pthread_rwlock_destroy(&rwlock);
67
68     return 0;
69 }

条件变量

条件变量是另一种逻辑稍微复杂一点点的同步互斥机制,他必须跟互斥锁一起配合使用

api

初始化、销毁条件变量

跟其他的同步互斥机制一样,条件变量的开始使用之前也必须初始化。初始化函数中的 属性参数 attr 一般不使用,设置为 NULL 即可。当使用 pthread_cond_destroy( )销毁 一个条件变量之后,他的值变得不确定,再使用必须重新初始化

进入条件变量等待队列,同时对获取配套的互斥锁

以上两个函数功能是一样的,区别是 pthread_cond_timedwait( )可以设置超时时 间。着重要注意的是:一旦进入条件变量 cond 的等待队列,互斥锁 mutex 将立即被加锁。

唤醒全部,或者一个条件变量等待队列中的线程

以上两个函数用来唤醒阻塞在条件变量等待队列里的线程,顾名思义,broadcast 用来唤醒全部的线程,相对地 signal 只唤醒一个等待中的线程。

注意:被唤醒的线程并不能立即从 pthread_cond_wait( )中返回,而是必须要先获得配套的互斥锁

例子

小楠是一名在校学生,每个月都会从父母那里得到一笔生活费。现在她的钱花光了,想 要去取钱。但是很显然取钱这样的事情不是想干就能干的,前提是卡里必须得有钱才行!于 是小楠拿起手机一查发现:余额为¥0。现在她除了干瞪眼,唯一能干的事情也许只有一件: 等。等到她爸妈汇了钱打电话通知她为止。 但更进一步,即便是她爸妈汇了钱也打了电话通知了她,此刻她也不能一定保证能取到 钱,因为与此同时她的众多兄弟姐妹(统统共用一个银行账号)很可能已经抢先一步将钱悉 数取光了!因此当小楠收到爸妈的电话之后,需要再次确认是否有钱,才能取钱

使用条件变量,来实现前面所述的小楠和她的兄弟姐妹取钱的逻辑

1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <pthread.h>
6
7 int balance = 0; // 所有线程共享的“余额”
8
9 pthread_mutex_t m;
10 pthread_cond_t v;
11
12 void *routine(void *args)
13 {
14     // 加锁,取钱
15     pthread_mutex_lock(&m);
16
17     while(balance < 100) // 若余额不足,则进入等待睡眠,顺便解锁
18     pthread_cond_wait(&v, &m);
19
20     fprintf(stderr, "t%d: balance = %d\n", (int)args, balance);
21     balance -= 100; // 取¥100 大洋
22
23     // 解锁,走人
24     pthread_mutex_unlock(&m);
25     pthread_exit(NULL);
26 }
27
28 int main(int argc, char **argv)
29 {
30     if(argc != 2)
31     {
32         printf("Usage: %s <threads-number>\n", argv[0]);
33         return 1;
34     }
35
36     pthread_mutex_init(&m, NULL);
37     pthread_cond_init(&v, NULL);
38
39     // 循环地创建若干条线程
40     pthread_t tid;
41     int i, thread_nums = atoi(argv[1]);
42     for(i=0; i<thread_nums; i++)
43     {
44         pthread_create(&tid, NULL, routine, (void *)i);
45     }
46
47     pthread_mutex_lock(&m); // 要往账号打钱,先加锁
48
49     balance += (thread_nums * 100); // 根据线程数目,打入¥
50     pthread_cond_broadcast(&v); // 通知所有正在等待的线程
51     pthread_mutex_unlock(&m);
52
53     pthread_exit(NULL);
54 }

可重入函数

一个函数如果同时被多条线程调用,他返回的结果是 否都是严格一致的?如果是,那么该函数被称为“可重入”函数, 否则被称为“不可重入”函数

多条线程同时调用函数有可能会产生不一致的结果,产生这样结果的原因有三

  • 一是因为函数内部使用了共享资源,比如全局变量、环境变量
  • 二是因为函数内部调用了其他不可重入函数
  • 三是因为函数执行结果与某硬件设备相关

原则

  • 不使用任何静态数据,只使用局部变量或者堆内存
  • 不调用上表中的任何非线程安全的不可重入函数
  • 如果不能同时满足以上两个条件,可以使用信号量、互斥锁等机制来确保使用静态数据 或者调用不可重入函数时的互斥效果

 

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

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

相关文章

UE Snap03 启动参数设置

UE Snap03 启动参数设置 UE打包后传入自定义参数及解析。 void UGameInstance::StartGameInstance() {Super::StartGameInstance();UE_LOG(LogTemp, Warning, TEXT("--StartGameInstance--"));FString param;FParse::Value(FCommandLine::Get(), TEXT("-UserN…

# 谷歌 Chrome 浏览器无法安装插件的解决方法

谷歌 Chrome 浏览器无法安装插件的解决方法 运用开发模式安装 安装步骤&#xff1a; 1、 将 XX.crx 插件的扩展名改成 .zip 或者 .rar 并解压到文件夹 XX 目录。 1&#xff09;如&#xff1a;下载的 前端框架 vue.js 插件 nhdogjmejiglipccpnnnanhbledajbpd-6.6.1-Crx4Chro…

Isaac Sim 2 (学习笔记4.26)

今天一整天都要开会&#xff0c;闲的无聊&#xff0c;把这周学的东西简单整理下。纯英文文档想不起来东西的时候总是找不到位置...持续更新一整天 1.将块与块连接起来 尝试连接块与块的时候发现只能是cube、mesh连接&#xff0c;如果是一整个的包括坐标系、材质包等等&#xf…

阿里云服务器购买和设置

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;服务器❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 1、搜索阿里云网址&#xff1a; 2、点击产品&#xff0c;选择云服务器ECS 3、选择立即购买 4、选…

C# Web控件与数据感应之 Control 类

目录 关于数据感应 Control 类 范例运行环境 simpleDataListEx方法 设计 实现 调用示例 数据源 调用 小结 关于数据感应 数据感应也即数据捆绑&#xff0c;是一种动态的&#xff0c;Web控件与数据源之间的交互&#xff0c;诸如 ListControl 类类型控件&#xff0c;在…

uni-app - 使用地图功能打包安卓apk的完美流程以及重要的注意事项(带您一次打包成功)

在移动应用开发中&#xff0c;地图功能是一个非常常见且实用的功能&#xff0c;可以帮助用户快速定位并浏览周边信息。而在uni-app开发中&#xff0c;使用地图功能也是一项必备技能。本文将介绍uni-app使用地图功能打包安卓apk的注意事项&#xff0c;帮助开发者顺利完成地图功能…

万兆以太网MAC设计(12)万兆UDP协议栈上板与主机网卡通信

文章目录 一、设置IP以及MAC二、上板效果2.1、板卡与主机数据回环测试2.2、板卡满带宽发送数据 一、设置IP以及MAC 顶层模块设置源MAC地址 module XC7Z100_Top#(parameter P_SRC_MAC 48h01_02_03_04_05_06,parameter P_DST_MAC 48hff_ff_ff_ff_ff_ff )(input …

excel图表如何忽略空值呢?

在excel柱形图和折线图中有多余的空值&#xff0c;如何不把空值当成0值处理&#xff0c;可以达到第二个图的效果? 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 用的excel2019已经是自动将这些空值忽略了&#xff0c;在低版本上&#xff0c;是会将空值…

2024年想创业做电商,视频号小店绝对是最明智的选择!

大家好&#xff0c;我是电商糖果 在电商行业摸爬滚打了七年时间了&#xff0c;做过天猫&#xff0c;京东&#xff0c;闲鱼。 目前在做的项目只有两个&#xff0c;一个是抖音小店&#xff0c;已经做了四年多了。 另一个就是视频号小店&#xff0c;现在做了有一年多了。 视频…

力扣HOT100 - 79. 单词搜索

解题思路&#xff1a; 深度优先搜索&#xff08;DFS&#xff09; 剪枝。 class Solution {public boolean exist(char[][] board, String word) {char[] words word.toCharArray();for(int i 0; i < board.length; i) {for(int j 0; j < board[0].length; j) {if (df…

docker compose安装redis

一、安装准备 在docker hub查看redis镜像版本。查看地址如下&#xff1a; Dockerhttps://hub-stage.docker.com/_/redis/tags 二、拉取docker镜像 我这里用redis:6.2.14版本&#xff0c;先拉取镜像。命令如下&#xff1a; docker pull redis:6.2.14 查看刚刚下载的镜像&am…

冯喜运:4.30现货黄金涨跌互现,最新黄金原油趋势分析

【黄金消息面分析】&#xff1a;上周五公布的数据&#xff0c;美国3月核心PCE年率维持前值不变&#xff0c;美国4月一年期通胀率预期上升&#xff0c;显示通胀顽固并有所回升&#xff0c;但其经济数据美国3月个人支出月率和美国4月密歇根大学消费者信心指数终值则低于预期和前值…

ABeam德硕受邀参加第四届碳交易与ESG投资合作发展大会并荣获2024 ESG“前沿奖”

ABeam荣获2024 ESG“前沿奖”&#xff0c;ABeam大中华区董事长兼总经理中野洋辅先生上台领奖 ABeam ESG News 3月28日&#xff0c;由中国金融前沿论坛&#xff08;CFAF&#xff09;主办&#xff0c;ABeam Consulting、MSCI、BCG、中金公司、方达律师事务所等合作协办的第四届…

Java包装类,128陷阱

包装类 基本数据类型都有自己对应的包装类&#xff0c;因为Java本质是面向对象编程的&#xff0c;一切的内容在Java看来都是对象 但是基本数据类型没有类&#xff0c;也没有对象&#xff0c;这样就有了矛盾 所以诞生了基本类型的包装类 基本数据类型&#xff1a; byte,short,…

Spring AI 来啦,快速上手

Spring AI Spring框架在软件开发领域&#xff0c;特别是在Java企业级应用中&#xff0c;一直扮演着举足轻重的角色。它以其强大的功能和灵活的架构&#xff0c;帮助开发者高效构建复杂的应用程序。而Spring Boot的推出&#xff0c;更是简化了新Spring应用的初始搭建和开发过程…

(超全)python图像处理详细解析(1)

图像处理 skimage包的子模块1.读取图像2.图像灰度处理3.加载程序自带图像4.查看存储路径5.保存图片6.图片信息7.输出小猫图片的G通道中的第20行30行列的像素值8.显示红色单通道图片9.对小猫图片添加椒盐噪声10.高斯去噪11.中值滤波去噪12.随机生成噪声点13.对小猫图像进行裁剪1…

vue报错:Do not mutate vuex store state outside mutation handlers.

vue报错&#xff1a;Do not mutate vuex store state outside mutation handlers. 原因&#xff1a;在vuex store的state外部直接修改了state的值&#xff0c;但是Vuex要求所有的state的修改必须在vuex中&#xff0c;不允许直接咋组件中修改store中的状态&#xff0c;除非通过m…

ios CI/CD 持续集成 组件化专题五-(自动发布私有库-组件化搭建)

一&#xff1a;手动发布私有库总结 手动发布pod私有库&#xff0c;需要进行如下几步操作&#xff1a; 1、修改完代码之后&#xff0c;需要提交代码push到git仓库。 2、给代码打tag。 3、修改podspec文件的version值&#xff0c;使其和设置的tag一直。 4、命令行执行pod repo…

【Java EE】日志框架(SLF4J)与门面模式

文章目录 &#x1f340;SLF4j&#x1f333;门面模式(外观模式)&#x1f338;门面模式的定义&#x1f338;门面模式的模拟实现&#x1f338;门面模式的优点 &#x1f332;关于SLF4J框架&#x1f338;引入日志门面 ⭕总结 &#x1f340;SLF4j SLF4J不同于其他⽇志框架,它不是⼀个…

分子动力学模拟学习-Gromacs工具链

1、总体流程 在gromacs的使用说明中有一个flow chart&#xff0c;比较简略。以下针对一般体系&#xff08;非蛋白等领域&#xff09;进行了一些调整&#xff0c;通用性更强。 在做分子动力学模拟时&#xff0c;其复杂性除了以上各种输入输出文件的操作&#xff0c;另一点就是力…