相同的条件下,同一份程序,多次执行结果不同,执行结果有二义性,说明该线程是不安全的,那么怎样使线程变得安全就成了一个值得研究的问题。
下面我们以一个例子来说明这个问题;
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<pthread.h>
int a=0;
void *fun(void *arg)
{
int i=0;
for(;i<1000;++i)
{
++a;
}
}
void fun2()
{
printf("a=%d\n",a);
}
int main()
{
atexit(fun2);
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);
assert(res==0);
int i=0;
for(;i<1000;++i)
{
++a;
}
pthread_exit(NULL);
}
这个程序的流程如下:
1、从内存中获取期初值
2、CPU执行++a操作
3、将执行结果存储到内存上
线程出现安全问题的原因:
- 线程是并发执行的或者是并行的
- 在并发系统上——操作是非原子操作
- 在并行系统上——操作是非互斥的
- 操作对象是同一个,线程之间数据是共享的(.data .bss)
因为线程之间共享全局数据、静态数据。在编程的过程中,我们必须对线程访问的这些资源做一些同步控制,但有些系统调用或库函数在实现时,用到了静态的数据,当在多线程环境中调用这些函数时,就会出现不安全的现象,对于这些函数,在多线程环境中必须使用它们的安全版本,即可重入版本。
这里以字符串分割函数char *strtok(char *sourstr,const char *flag);为例:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<pthread.h>
void *fun(void *arg)
{
char buff[128]="a b c d e f g h i j k";
char *p=strtok(buff," ");
while(p!=NULL)
{
printf("fun:%c\n",*p);
p=strtok(NULL," ");
sleep(1);
}
}
int main()
{
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);
assert(res==0);
char buff[128]="1 2 3 4 5 6 7 8 9";
char *p=strtok(buff," ");
while(p!=NULL)
{
printf("main:%c\n",*p);
p=strtok(NULL," ");
sleep(1);
}
pthread_exit(NULL);
}
这里我们可以看到它的运行结果出现了二义性。
char *strtok(char *sourstr,const char *flag);使用静态变量,静态变量属于共享资源,会操作同一个对象,线程不安全。
我们就需要用它的可重入版本:
char *strtok_r(char *sourstr,const char *flag,char **res);