线程安全——使用线程安全函数,多线程中执行fork引发的问题及如何解决

news2024/11/20 0:30:47

目录

一、引例

二、线程安全

三、多线程中执行fork

3.1 多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

3.2 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁


一、引例

在主线程和函数线程中进行语句分割并输出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>

void* thread_fun(void* arg)
{
	char buff[128]={"a b c d e f g h w q"};
	char* s=strtok(buff," ");
	while(s!=NULL)
	{
		printf("thread:s=%s\n",s);
		sleep(1);
		s=strtok(NULL," ");
	}
}

int main()
{
	pthread_t id;
	pthread_create(&id,NULL,thread_fun,NULL);
	char str[128]={"1 2 3 4 5 6 7 8 9 10"};
	char* s=strtok(str," ");
	while(s!=NULL)
	{
		printf("main:%s\n",s);
		sleep(1);
		s=strtok(NULL," ");
	}
	pthread_join(id,NULL);
	exit(0);
}

因为strtok函数不是线程安全的,因为它使用了静态变量或者全局变量。

只要使用全局变量或者静态变量的函数,在多线程中都不能使用。这些函数都不是线程安全的。

不可重入:当程序被多个线程反复调用,产生的结果会出错。

strtok_r函数是线程安全的

更改后代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>

void* thread_fun(void* arg)
{
	char buff[128]={"a b c d e f g h w q"};
	char* ptr=NULL;
	char* s=strtok_r(buff," ",&ptr);
	while(s!=NULL)
	{
		printf("thread:s=%s\n",s);
		sleep(1);
		s=strtok_r(NULL," ",&ptr);
	}
}

int main()
{
	pthread_t id;
	pthread_create(&id,NULL,thread_fun,NULL);
	char str[128]={"1 2 3 4 5 6 7 8 9 10"};
	char* ptr=NULL;
	char* s=strtok_r(str," ",&ptr);
	while(s!=NULL)
	{
		printf("main:%s\n",s);
		sleep(1);
		s=strtok_r(NULL," ",&ptr);
	}
	pthread_join(id,NULL);
	exit(0);
}

 

二、线程安全

 线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么就说这些线程是安全的。
要保证线程安全需要做到:
1)对线程同步,保证同一时刻只有一个线程访问临界资源.

2)在多线程中使用线程安全的函数(可重入函数)

所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们称它是线程安全的。

三、多线程中执行fork

3.1 多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>

void* fun(void* arg)
{	
	for(int i=0;i<5;i++)
	{
		printf("fun run pid=%d\n",getpid());
		sleep(1);
	}
}

int main()
{
	pthread_t id;
	pthread_create(&id,NULL,fun,NULL);

	fork();
	for(int i=0;i<5;i++)
	{
		printf("main run pid=%d\n",getpid());
		sleep(1);
	}
}

结论:fork()以后,不管父进程有多少条执行路径,子进程只有一条执行路径,这条路径就是fork所在的那条执行路径;


3.2 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁

代码测试:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <wait.h>
pthread_mutex_t mutex;
void* fun(void* arg)
{
	pthread_mutex_lock(&mutex);
	printf("fun lock!\n");
	sleep(5);
	pthread_mutex_unlock(&mutex);
	printf("fun unlock!\n");	
}

int main()
{
	pthread_t id;
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&id,NULL,fun,NULL);

	sleep(1);//保证函数线程一定结束
	pid_t pid=fork();
	if(pid==-1)
	{
		exit(1);
	}
	if(pid==0)
	{
		printf("child lock start!\n");
		pthread_mutex_lock(&mutex);
		printf("child lock success!\n");
		pthread_mutex_unlock(&mutex);
		exit(0);
	}
	wait(NULL);
	pthread_join(id,NULL);
	printf("main over!\n");
	exit(0);
}

运行结果:(阻塞)

原因如下:

其实就是:fork之后锁的状态也一并被复制了.

但是因为多进程并发运行,你也不知道某一刻锁的状态到底是什么;

也就是锁的状态在子进程中是不清晰的;也就是子进程中锁的状态你也不清楚,那么我们怎么在子进程中使用锁呢?虽然你可以直接解锁,但是这么做意义就不对了,如果本来是在保护资源,你一来就解锁,那么程序就出现问题了

结论:
父进程有锁,子进程也被复制了锁;锁的状态取决于fork的那一刻父进程的锁的状态,也就是说锁的状态也会被复制进去子进程;

如何解决上述问题?

延迟fork的复制(有人用锁的时候等一等,没人用锁的时候再fork)

没有人用锁的时候我们再去fork;那么如何判断有没有人用锁呢?我们去加锁一下,如果没有成功,就是有人用锁.如果加锁成功,就是没有人用锁,这个时候再去fork;

而这个方法(在fork前后去加锁),它是有一个线程的方法可以完成的:pthread_atfork;

int pthread_atfork(void(*prepare)(void),void(*parent)(void),void(*child)(void));

三个参数:每个参数都是一个函数指针;指针指向参数为void,返回值也为void的函数;

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <wait.h>
pthread_mutex_t mutex;
//准备两个函数
void prepare(void)
{
	pthread_mutex_lock(&mutex);
}

void after(void)
{
	pthread_mutex_unlock(&mutex);
}

void* fun(void* arg)
{
	pthread_mutex_lock(&mutex);
	printf("fun lock!\n");
	sleep(5);
	pthread_mutex_unlock(&mutex);
	printf("fun unlock!\n");	
}

int main()
{
	pthread_t id;
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&id,NULL,fun,NULL);
	pthread_atfork(prepare,after,after);//放在锁的初始化后面,fork之前即可

	sleep(1);//保证函数线程一定结束
	pid_t pid=fork();
	if(pid==-1)
	{
		exit(1);
	}
	if(pid==0)
	{
		printf("child lock start!\n");
		pthread_mutex_lock(&mutex);
		printf("child lock success!\n");
		pthread_mutex_unlock(&mutex);
		exit(0);
	}
	wait(NULL);
	pthread_join(id,NULL);
	printf("main over!\n");
	exit(0);
}

  

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

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

相关文章

vite+ts+vue3 项目搭建和基础配置

官网: 文件夹cdm进入小黑窗, 按步骤安装项目即可, 用到的都选 "是" 快速上手 | Vue.jsVue.js - 渐进式的 JavaScript 框架https://cn.vuejs.org/guide/quick-start.html安装成功后目录如下: index.html 尾部报错: cleareslint(vue/comment-directive), 已知 是eslin…

新书速览|FFmpeg开发实战:从零基础到短视频上线

资深音视频开发专家、畅销书作者重磅新作&#xff0c;从基础知识到高级应用&#xff0c;从桌面开发到移动开发&#xff0c;9大实际音视频项目完整再现 本书内容 《FFmpeg开发实战&#xff1a;从零基础到短视频上线》是一本FFmpeg开发的实战教程&#xff0c;由浅入深&#xff0…

vite+vue3门户网站菜单栏动态路由控制

门户网站用户端需要分板块展示&#xff0c;板块内容由管理端配置&#xff0c;包括板块名称&#xff0c;访问路径&#xff0c;路由组件&#xff0c;展示顺序&#xff0c;是否展示。如下图所示&#xff1a; 用户访问门户网站时&#xff0c;展示菜单跳转通过板块配置&#xff0c;动…

Leetcode : 1137. 高度检查器

学校打算为全体学生拍一张年度纪念照。根据要求&#xff0c;学生需要按照 非递减 的高度顺序排成一行。 排序后的高度情况用整数数组 expected 表示&#xff0c;其中 expected[i] 是预计排在这一行中第 i 位的学生的高度&#xff08;下标从 0 开始&#xff09;。 给你一个整数…

Web3 Summit 2024 柏林重启:与全球 Buidler 共赴创新盛事

时隔五年&#xff0c;Web3 Summit 将于 8 月 19 日至 21 日回归柏林举办。 2019 年&#xff0c;1000 多名开发人员、研究人员和 buidler 齐聚一堂&#xff0c;参加了上一届为期三天的 Web3 Summit&#xff0c;这是一个所有致力于促进在去中心化网络上深耕团队相聚的盛事。 与…

RDB 和 AOF 的实现原理以及优缺点

一个工作了 5 年的粉丝私信我&#xff0c; 关于 RDB 和 AOF 的实现原理 这个问题在面试的时候&#xff0c;应该怎么回答&#xff1f;于是我把之前整理过的一个高手回答整理成文档发给了他&#xff0c;后来他参考这个回复在面试的时候顺利拿到了 offer 今天我把这个文档分享给大…

AI大模型,掀起新一波智能浪潮!

AI大模型的出现&#xff0c;标志着人工智能技术迈入了一个新的阶段。这些巨大的模型不仅在规模上超越了以往任何其他人工智能系统&#xff0c;而且在性能上也取得了巨大的突破。由于其庞大的参数量和复杂的结构&#xff0c;AI大模型在各个领域展现出了强大的学习能力和推理能力…

Haproxy 负载均衡集群

一. Haproxy 1. Haproxy 介绍 HAProxy 是法国开发者威利塔罗 (Willy Tarreau) 在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则…

基于React低代码平台开发:直击最新应用构建

文章目录 前言一、React与低代码平台的结合优势二、基于React的低代码平台开发挑战三、基于React的低代码平台开发实践四、未来展望《低代码平台开发实践&#xff1a;基于React》编辑推荐内容简介作者简介目录前言为什么要写这本书读者对象如何阅读本书 前言 随着数字化转型的…

【免费资源】Unity真实广阔的沙漠场景等你来解锁!

Unity真实广阔的沙漠场景等你来解锁&#xff01; Unity 每周免费资源上新啦&#xff01;此次更新的是广阔的沙漠场景&#xff0c;其中包含 14 个预制体&#xff0c;每个预制体都包含 LOD、400-2000 顶点和 4K 纹理。现在&#xff0c;只需登录 Asset Store&#xff0c;即可免费领…

ThinkPHP6与支付宝支付集成指南:轻松实现在线收款!

随着移动支付的普及&#xff0c;支付宝成为了越来越多人的首选支付方式。而作为一款高性能、高效率、安全稳定的开源框架&#xff0c;thinkphp6也被众多开发人员所青睐。那么&#xff0c;如何在thinkphp6中快速简便地实现支付宝支付呢&#xff1f; 首先&#xff0c;我们需要在…

openGauss学习笔记-238 openGauss性能调优-SQL调优-SQL执行计划介绍-详解

文章目录 openGauss学习笔记-238 openGauss性能调优-SQL调优-SQL执行计划介绍-详解238.1 详解238.1.1 执行计划238.1.1.1 执行计划层级解读&#xff08;纵向&#xff09;238.1.1.2 执行计划中的关键字说明 238.1.2 执行信息 openGauss学习笔记-238 openGauss性能调优-SQL调优-S…

机器学习-可解释性机器学习:随机森林与fastshap的可视化模型解析

一、引言 机器学习在当今社会扮演着日益重要的角色&#xff0c;但黑盒模型的不可解释性限制了其应用范围。因此&#xff0c;可解释性机器学习成为研究热点&#xff0c;有助于提高模型的可信度和可接受性。本文旨在探讨随机森林和fastshap作为可视化模型解析工具的应用&#xff…

nodejs web服务器 -- 搭建开发环境

一、配置目录结构 1、使用npm生成package.json&#xff0c;我创建了一个nodejs_network 文件夹&#xff0c;cd到这个文件夹下&#xff0c;执行&#xff1a; npm init -y 其中-y的含义是yes的意思&#xff0c;在init的时候省去了敲回车的步骤&#xff0c;如此就生成了默认的pac…

Mysql学习笔记之事务详解(读未提交、读以提交、可重复读、串行化读)

在这个博主的基础上&#xff0c;增加两种情况的对比&#xff1a;https://blog.csdn.net/llllllkkkkkooooo/article/details/108068919 可重复读中幻读现象&#xff08;未使用MVCC&#xff09; 设置可重复读的隔离级别 set global transaction isolation level repeatable read…

Java --- springcloud初始项目创建

目录 一、cloud项目创建 1.1、项目编码规范 1.2、注解生效激活 1.3、导入父工程maven的pom依赖 二、创建子工程并导入相关pom依赖 2.1、相关配置文件 2.1.1、数据库配置文件内容 2.1.2、自动生成文件配置内容 三、创建微服务8001子工程 3.1、导入相关pom依赖 3.…

Orange3数据预处理(分组组件)

Group By是Orange3中一个非常有用的组件&#xff0c;它允许用户对数据集进行聚合操作&#xff0c;类似于SQL中的GROUP BY语句或Pandas库中的groupby方法。以下是Group By组件的一些核心功能介绍&#xff1a; 1. Mean (平均数): 数据值的总和除以数据项的数量&#xff0c;显示数…

yolov8多batch推理,nms后处理

0. 背景 在高速公路监控视频场景下&#xff0c;图像分辨率大都是1920 * 1080或者2560 * 1440&#xff0c;远处的物体&#xff08;车辆和行人等&#xff09;都比较小。考虑需要对图像进行拆分&#xff0c;然后把拆分后的数据统一送入模型中&#xff0c;推理的结果然后再做nms&am…

有没有能用蓝牙的游泳耳机?6招解决选购难题,瞄准好货!

在数字化时代&#xff0c;我们越来越依赖各种电子设备来提升生活质量。对于喜欢运动的朋友来说&#xff0c;耳机已经成为他们必不可少的装备之一。无论是跑步、健身还是游泳&#xff0c;耳机都能为我们提供美妙的音乐和清晰的语音通话&#xff0c;让我们的运动体验更加丰富多彩…