一、线程安全的概念
线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是 一样的、正确的。那么就说这些线程是安全的。
二、如何保证线程安全
1.线程同步
保证同一时刻只有一个线程访问临界资源。线程同步的方法有4种,信号量、互斥锁、读写锁、条件变量。
2.使用线程安全的函数
在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
三、线程安全的函数的应用
【例】在两个线程中分别对一个字符串进行分隔的问题
字符串分隔函数:
第一次调用结束后,第二次调用的时候给第一个参数传空,就可以沿着原来的字符串继续分隔拿到第二段分割好的字符串。
第一次没有使用线程安全函数的情况的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
void* fun(void*arg)
{
char arr[128]="a b c d e f";//定义一个字符串
//以空格作为字符串buff的分隔符,将分隔好的字符串内容返回给s
char* s =strtok(arr," ");
while(s!=NULL)//如果s为空结束循环,s为空说明字符串buff已经被分隔完了
{
printf("fun分隔字符串arr得到的内容为:%s\n",s);
s=strtok(NULL," ");
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
char brr[128]="1 2 3 4 5 6";
char* p =strtok(brr," ");
while(p!=NULL)
{
printf("main分隔字符串brr得到的内容为:%s\n",p);
p=strtok(NULL," ");
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
运行结果:
结果分析:
这样的运行结果并不是我们所期望的结果,我们希望fun函数分隔字符串arr,main函数分隔字符串brr,但是上面的程序中,main函数却去分隔了fun函数中的字符串arr,子线程fun和主线程main之间彼此影响,然而这并不是我们所期望的。
出现上面结果的原因是,strtok()函数的内部有一个指针用来记录把字符串分隔到哪个位置,这个指针大概率是一个静态变量指针,这样做有一个弊端,就是在线程fun和线程main中调用strtok()这个函数的时候访问静态变量是同一快空间,当线程fun中去调用这个strtok()函数去记录当前的字符串arr被分隔到了哪个位置,但是在线程main中也调用了strtok()这个函数去记录当前的字符串brr被分隔到了哪个位置,意味着线程fun和线程main都要去修改同一块空间中的内容。也就是说,fun线程和main线程都在调用strtok()这个函数,在这两个线程中调用strtok()函数的两条路径同时在执行,strtok()函数中的那个记录字符串分隔位置的指针是一个静态变量,我们在程序中看到在线程fun和main中好像各自都给strtok()函数的这个静态变量分配了一块空间,但是其实不是的,因为在物理内存空间上,这两个线程中strtok()函数的这两块静态变量的空间用的是同一块空间。所以,如果在某一个线程中修改了这块空间中静态变量的值,那么对于另外一个线程来说,这个静态变量的值也发生了变化,因此,这两个线程在运行的时候就会彼此影响,导致最终输出的结果并不是我们所期望的。也就是如果要在多线程中去同时strtok()方法,那么是不可以的,因为strtok()这个方法不是线程安全的。strtok()方法压根就不可以在多线程中使用,因为strtok()函数内部有静态变量,在多线程中同时执行得话,每条路径访问到的静态变量就是同一个,每个线程去记录这个静态变量得信息的时候就会冲突,信息就会被覆盖,程序的执行结果就会产生问题。
比如上图的结果就是因为,main线程和fun线程中调用strtok()函数这两条路径同时执行,第一次分隔之后都得到了对应被分隔的字符串,但是第二次分隔的时候,因为传入的参数为NULL,要从上一次结束的位置去继续分隔字符串,由于上一次分隔字符串结束的位置在fun线程中的arr字符串中,所以当main线程执行的时候,它接收到的被分隔好的字符串是从arr中分隔来的,后续分隔的字符串也都是对arr字符串进行分隔。解决这个问题的思路就是,让两个线程自己用自己的空间,分别记录当前字符串被分隔到哪里,就需要用到这个函数的线程安全版本strtok_r()。
有一类库函数之前都不是线程安全的,引用多线程之后都会存在问题,为之实现了一个多线程的版本,称之为线程安全的版本。这样的函数有很多,比如当前的strtok()函数,它的线程安全版本如下:
第三个参数 saveptr:是一个二级指针,它需要传入一个指针,传入一个指针以后,通过传入得指针来记录当前字符串被分隔到哪里。这样的话每一个线程都会自己定义这样一个指针,来分别记录它们分隔得字符串分隔到哪个位置,这时,两个线程就不共用同一块空间了。
使用线程安全函数strtok_r()的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>
void* fun(void*arg)
{
char arr[128]="a b c d e f";//定义一个字符串
//以空格作为字符串buff的分隔符,将分隔好的字符串内容返回给s
char* n=NULL;//定义一个空指针,用这个指针就用来记录字符串分隔的位置
char* s =strtok_r(arr," ",&n);
while(s!=NULL)//如果s为空结束循环,s为空说明字符串buff已经被分隔完了
{
printf("fun分隔字符串arr得到的内容为:%s\n",s);
s=strtok_r(NULL," ",&n);
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
char brr[128]="1 2 3 4 5 6";
char* m=NULL;
char* p =strtok_r(brr," ",&m);
while(p!=NULL)
{
printf("main分隔字符串brr得到的内容为:%s\n",p);
p=strtok_r(NULL," ",&m);
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
运行结果:
此时,就是fun线程和main线程不再共用一块空间了,它们各自有各自的空间来来记录自己所分隔的字符串分隔到了哪个位置。这样两个线程同时执行的时候彼此之间就不会影响了,程序也不会产生错误的结果。
四、总结
在多线程中使用库函数的时候一定要使用线程安全版本,这样才能保证线程安全。线程安全指的就是,多线程程序无论调度顺序如何,都能保证程序的结果是正确的,就说该程序处于线程安全的状态。主要的方法就是线程同步和线程安全函数。