目录
做法:线程同步、线程安全(可重入)函数解决
strtok_r与strtok的不同是添加了第三个参数:**saveptr,局部变量进行标记。
代码实现用线程fun内对数组buff获取每个字符’a‘,'b','c'....。在主线程内对数组arr获取每个字符'1','2','3'...,然后调用线程fun:
- #include
- #include
- #include
- #include
- #include
-
- 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数组的字符?
为什么第一组打印的是正确的?
因此解决方案就是引入线程安全版本函数
这些库函数之所以不可重入,主要是因为使用了静态变量。Linux对很多不可重入的库函数提供了对应的可重入版本,在函数名尾部加_r。在多线程程序中调用库函数,一定要使用其可重入版本,否则可能导致预想不到的结果。
用代码实现程序创建了2条线程,即2个执行路径,在其中一条执行路径中fork,fork出对当前路径的进程复制,然后观察for打印的pid与哪个进程的pid一致,也就是查看fork复制后启动的是哪条路经。答案:fork之后,只启用1条执行路径,启用的路径是当前fork所在的执行路径。
- #include
- #include
- #include
- #include
- #include
-
- 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);
- //fork();
- 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);
- }
(1)fork在main内执行,主线程main被启用,执行结果如下,打印出主线程main的pid3669,而fun与main线程打印出进程pid3667,其中3668的pid被另一个线程执行耗掉了。
(2)fork在fun内执行,线程函数fun被启用,执行结果如下,fun与main线程打印出进程pid3712,被启动的线程fun打印的线程pid3714,其中3713的pid被另一个线程执行耗掉了。
如下代码说明在线程中先加锁再执行fork会出现的情况,其中主线程先创建了线程,然后fork,对子进程加锁解锁并输出提示信息,fun函数内执行加锁后休眠5秒,然后解锁并输出执行信息。
- #include
- #include
- #include
- #include
- #include
- #include
-
- 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);//保证fun函数线程先加锁
- 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);
- }
执行结果如下,进程被阻塞住,fun函数加锁(锁1)后,fork会把锁复制给子进程,复制过去的状态是复制时的状态即复制了一份已经被加锁了的锁(锁2),因此子进程无法对已经加锁的锁(锁2)进行二次加锁,所以即便fun解锁(锁1)之后,子进程内的锁(锁2)还是没有被解除,仍然无法使用。
解决方案:
在fork之前先加锁,此时加锁,并不是访问临界资源,而是检验是否锁是空闲状态,如果是空闲的状态就可以对其加锁:
- #include
- #include
- #include
- #include
- #include
- #include
-
- 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);
- }