考点,线程同步有哪些操作:
一、概念
- 如果一个函数能被多个线程同时调用且不发生竞态条件,则成为它是线程安全,也叫可重入函数。
- 通俗地说就是多线程程序无论调度顺序怎么样都可以得到正确的结果,运行时程序不出错。
- 可重入也就是在调用一次未执行结果又重新调用。
二、如何保障安全
做法:线程同步、线程安全(可重入)函数解决
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);
}