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

目录

一、引例

二、线程安全

三、多线程中执行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);
}

  

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值