目录
一、引例
二、线程安全
三、多线程中执行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);
}