【线程】多线程安全

news2025/1/11 8:44:17

考点,线程同步有哪些操作:

一、概念

  • 如果一个函数能被多个线程同时调用且不发生竞态条件,则成为它是线程安全,也叫可重入函数。
  • 通俗地说就是多线程程序无论调度顺序怎么样都可以得到正确的结果,运行时程序不出错。
  • 可重入也就是在调用一次未执行结果又重新调用。

二、如何保障安全

做法:线程同步、线程安全(可重入)函数解决

1.strtok_r函数举例说明

strtok_r与strtok的不同是添加了第三个参数:**saveptr,局部变量进行标记。

代码实现用线程fun内对数组buff获取每个字符’a‘,'b','c'....。在主线程内对数组arr获取每个字符'1','2','3'...,然后调用线程fun:

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

void* fun(void*arg)
{
	char buff[]={"a b c d e f g h"};
	char* ptr=NULL;
	char *s=strtok_r(buff," ",&ptr);//传地址,要修改
	while(s!=NULL)
	{
		printf("fun s=%s\n",s);
		sleep(1);
		s=strtok_r(NULL," ",&ptr);
	}
}

int main()
{
	pthread_t id;
	pthread_create(&id,NULL,fun,NULL);
	char arr[]="1 2 3 4 5 6 7 8";
	char *ptr=NULL;
	char * s=strtok_r(arr," ",&ptr);
	while(s!=NULL)
	{
		printf("main s=%s\n",s);
		sleep(1);
		s=strtok_r(NULL," ",&ptr);
	}
	pthread_join(id,NULL);
}

代码结果:符合预期。 

用strtok 不是重载函数的版本进行说明:
        利用strtok函数对数组按照分隔符,获取单个字符。值得说明的说strtok函数中有个内部指针用来标记走到哪个位置,而该指针的生存期超越函数,是个静态全局变量,在栈上不会被回收。

代码运行结果为:

解析:

为什么除了第一组外,其余的main内打印的是buff数组的字符?

  • 因为strtok内只有一个全局变量指针,只有一份指针时,当线程fun启动时,指针指向buff数组的位置,主线程的传递为null,默认指向fun内传递数组名buff的位置,而指针只能记住一个位置,它会指向最后一个赋值的指针,因此,后续main也指向buff,打印buff内的数组。

为什么第一组打印的是正确的?

  • 正确打印是因为第一次调用strtok时,fun和main都传了数组名,不用指针自己指向,而后续调用传递的第一个参数是NULL,需要指针自己找位置就出错了

因此解决方案就是引入线程安全版本函数

这些库函数之所以不可重入,主要是因为使用了静态变量。Linux对很多不可重入的库函数提供了对应的可重入版本,在函数名尾部加_r。在多线程程序中调用库函数,一定要使用其可重入版本,否则可能导致预想不到的结果。

 注意:全局变量注意不得轻易在线程中调用

2.fork举例说明

代码实现程序创建了2条线程,即2个执行路径,在其中一条执行路径中fork,fork出对当前路径的进程复制。

启用的执行路径

进程id
 

复制后,只启用1条执行路径,启用的路径是当前fork所在的执行路径。

fork执行,产生的子进程不一定非要主程序啥的

子进程一条执行路径----fork所在的路径

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

void* fun(void*arg)
{
	fork();
	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);
	for(int i=0;i<5;i++)
	{
		printf("main run pid%d\n",getpid());
		sleep(1);
	}

	char *ptr=NULL;
	pthread_join(id,NULL);
	exit(0);
}

main内:

 fun内:

 

先锁再fork会出现?

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/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(0);
	}
	if(pid==0)
	{
		printf("子进程即将加锁\n");
		pthread_mutex_lock(&mutex);
		printf("子进程加锁成功\n");
		pthread_mutex_unlock(&mutex);
	}
	else
	{
		wait(NULL);
		printf("main over\n");
	}
	pthread_join(id,NULL);
	exit(0);
}

fork会把锁赋值给子进程

赋值过去的状态是赋值时的状态——已加锁

解决方案:

fork之前先加锁,并不是访问临界资源,而是检验是否锁是空闲状态

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/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");
}
void at_lock(void)//加锁
{
	pthread_mutex_lock(&mutex);
}
void at_unlock(void)
{
	pthread_mutex_unlock(&mutex);
}
int main()
{
	pthread_t id;
	pthread_atfork(at_lock,at_unlock,at_unlock);//父进程解锁、子进程解锁
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&id,NULL,fun,NULL);

	sleep(1);
	pid_t pid=fork();

	if(pid==-1)
	{
		exit(0);
	}
	if(pid==0)
	{
		printf("子进程即将加锁\n");
		pthread_mutex_lock(&mutex);
		printf("子进程加锁成功\n");
		pthread_mutex_unlock(&mutex);
	}
	else
	{
		wait(NULL);
		printf("main over\n");
	}
	pthread_join(id,NULL);
	exit(0);
}

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

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

相关文章

2023年浙大MBA项目的后备考阶段三大策略:你永远要相信光的力量

有些人&#xff0c;走着走着就掉队了。距离今年的管理类联考还有一个月时间&#xff0c;一切看似终成定局&#xff0c;但实际上仍有很大转圜空间。对浙大MBA的准考生来说&#xff0c;走到目前这一步&#xff0c;剩下的一个月时间务必要坚持走完&#xff0c;而且要更加的勇敢和底…

SDN功能实现(四)--- 实现自定义action(1)修改OVS源码<队列去重(内核态实现)>

实现功能&#xff1a;设计一个新的action&#xff0c;实现在冗余链路中的数据包去重 一&#xff1a;在内核级定义OVS action &#xff08;一&#xff09;在datapath/linux/compat/include/linux/openvswitch.h中添加&#xff1a; enum ovs_action_attr {/* ... *//** after…

SparkStreaming

sparkstreaming 1.批处理与流处理 spark本身作为引擎时是批处理&#xff0c;从信息源全部读取数据&#xff0c;然后一批一批处理数据。处理sparkSQL等之后再存入hdfs。 sparkstreaming是实时引擎&#xff0c;在一个窗口时间内&#xff08;比如1s&#xff09;积攒数据&#x…

Spring IOC源码:实例化前的准备工作

前言 上篇文章我们讲解了IOC比较重要的后置处理器注册方法&#xff0c;本篇文章讲解实例化前的准备工作&#xff0c;包括国际化、多播器创建、监听器注册等节点。 正文 进入refresh方法中&#xff0c;可以看到在正式实例化初始化方法前&#xff0c;还有4个方法&#xff1a; …

Day09--小程序API的Promise化

1.基于回调函数的异步API的缺点 ************************************************************************************************************** 2.啥子是API Promise化呢&#xff1f; *****************************************************************************…

【Java八股文总结】之MyBatisPlus

文章目录MybatisPlus一、MyBatis Plus介绍1、Mybatis 和 Mybatis Plus 的区别Q&#xff1a;MyBatis的优缺点Q&#xff1a;MyBatis Plus的优点Q&#xff1a;MyBatis-Plus中的lambda表达式&#xff1f;Q&#xff1a;MyBatis中的动态标签有哪些&#xff1f;2、MyBatis Plus常用注解…

ZYNQ之FPGA学习----RAM IP核使用实验

1 RAM IP核介绍 RAM 的英文全称是 Random Access Memory&#xff0c; 即随机存取存储器&#xff0c; 它可以随时把数据写入任一指定地址的存储单元&#xff0c;也可以随时从任一指定地址中读出数据&#xff0c;其读写速度由时钟频率决定 Xilinx 7 系列器件具有嵌入式存储器结…

【OpenCV 例程 300篇】248. 特征描述之HOG描述符

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】248. 特征描述之HOG描述符 1. 方向梯度直方图 方向梯度直方图&#xff08;Histogram of Oriented Gradient, HOG&#xff09;使用梯度方向的分布作为特征来构造描述符&#xff0c;应用非常广泛。 梯…

07-HTTPS双向认证及Java案例

1.双向认证流程 客户端发起建立HTTPS连接请求&#xff0c;将SSL协议版本的信息发送给服务端&#xff1b;服务器端将本机的公钥证书&#xff08;server.crt&#xff09;发送给客户端&#xff1b;客户端读取公钥证书&#xff08;server.crt&#xff09;&#xff0c;取出了服务端公…

wordpress的手工迁移

我的场景 将某个在阿里云服务器&#xff08;windows操作系统&#xff09;上apache容器下的wordpress服务迁移到另一个linux主机上的apache上。 迁移要点 1、迁移源主机下的wordpress文件夹&#xff0c;在apache容器下的htdocs文件夹中 2、迁移数据库 3、根据目标&#xff0…

表弟大学毕业要学前端,我给他制定了一份亲属自学计划

表弟也终于到了马上要大学毕业的时间&#xff0c;然后听说我在做前端开发工作&#xff0c;就想着能不能和我一起搞一搞。 我说这又不是小时候一起去地里抓兔子&#xff0c;说走就一起走&#xff0c;拿上工具一起走了&#xff0c;这得学啊。看着表弟期待的眼神&#xff0c;他问了…

Scientific Reports|比较转录组分析揭示了杀菌剂氰烯菌酯对尖孢镰刀菌的抗性调控机制和杀菌活性

TITLE&#xff1a;Comparative transcriptome analysis reveals the resistance regulation mechanism and fungicidal activity of the fungicide phenamacril in Fusarium oxysporum 译名&#xff1a;比较转录组分析揭示了杀菌剂氰烯菌酯对尖孢镰刀菌的抗性调控机制和杀菌活性…

Java代码审计——文件操作漏洞

目录 &#xff08;一&#xff09;、 文件操作漏洞简介 &#xff08;二&#xff09; 、漏洞发现与修复案例 2.1 文件包含漏洞 2.2 文件上传漏洞 &#xff08;三&#xff09; 文件下载/读取漏洞 &#xff08;四&#xff09;&#xff0e;文件写入漏洞 &#xff08;五&…

Arcgis建筑面shp由DSM和DEM获取高度拉伸并可视化

效果 1、准备数据 DEM、DSM数据精度尽量高一些 1)DEM 2)DSM 3)建筑shp 所有数据坐标统一,而且加载后位置能对上,DEM和DSM具有相同的像元大小 2、准备数据前的一些操作 1)矢量shp裁剪

C#实现最大公约数和最小公倍数

最大公约数&#xff1a; 最大公因数&#xff0c;也称最大公约数、最大公因子&#xff0c;指两个或多个整数共有约数中最大的一个。a&#xff0c;b的最大公约数记为&#xff08;a&#xff0c;b&#xff09;&#xff0c;同样的&#xff0c;a&#xff0c;b&#xff0c;c的最大公约…

net.sf.json.JSONObject 类的日常使用,非阿里巴巴的JSONObject,附上作者的jsonDemo

文章目录Json介绍作者的Demo项目地址常见的转化使用测试json的添加属性&#xff0c;打印bean与json互转deepBean与json互转list与json互转map与json互转demo所用到的实体类StudentGrade个人使用的依赖常用方法其他参考文档Json介绍 1、JSONObject只是一种数据结构&#xff0c;可…

DJYGUI系列文章七:GDD窗口系统

目录 1 窗口分类及关系 2 窗口的客户区与非客户区 3 坐标系统 4 窗口句柄与窗口ID的作用与区别 5 窗口的关闭、销毁、退出过程 6 API说明 6.1 ScreenToClient&#xff1a; 屏幕坐标转换为客户区坐标 6.2 ClientToScreen&#xff1a; 客户区坐标转换为屏幕坐标 6.3 Scre…

linux篇【11】:linux下的线程

目录 一.linux下的线程 1.linux下的线程概念 &#xff08;1&#xff09;教材上粗略的 线程 定义 &#xff08;2&#xff09;线程的引入 &#xff08;3&#xff09;线程真正定义 以及 示意图 &#xff08;4&#xff09;linux 和 windows等其他操作系统的线程对比 2.重新定…

22-python异常

异常一. 了解异常二. 异常的写法2.1 语法2.2 快速体验2.3 捕获指定异常2.3.1 语法2.3.2 体验2.3.3 捕获多个指定异常2.3.4 捕获异常描述信息2.3.5 捕获所有异常2.4 异常的else2.5 异常的finally三. 异常的传递四. 自定义异常五. 总结一. 了解异常 当检测到一个错误时&#xff…

Hibernate多表的关联关系、懒加载

一、一对多关系&#xff1a;插入&#xff1a; “一”的一方为主表&#xff0c;“多”的一方为副表&#xff0c;主表关联副表&#xff0c;应该在主表中加入副表对象作为属性。 根据顾客ID插入顾客信息 &#xff08;一&#xff09; &#xff0c;同时将顾客名下所有订单插入 &…