线程安全
概念
线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的,那么就说这些线程是安全的。
要保证线程安全需要做到:
- 对线程同步,保证同一时刻只有一个线程访问临界资源
- 在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竞态时间,则我们称它是线程安全的。
由于线程是并发运行的,并且一个进程中的线程之间共享地址空间、如果这个多线程的程序运行过程中存在多线程去修改共享的数据时,会造成执行结果的二义性。
多线程修改共享的数据
——使用线程同步(互斥锁)来解决
示例代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int gdata = 1;//.data段保存的数据
void *fun(void *arg)
{
int i = 0;//fun函数的栈区定义的变量
for(;i < 10000;i++)
{
gdata++;
}
}
int main()
{
pthread_t id;
int res = pthread_create(&id,NULL,fun,NULL);
assert(res == 0);
//下面的代码由主线程和函数线程并发执行
int i = 0;//main函数栈区定义的变量
for(;i < 10000;i++)
{
gdata++;//1.读取内存当前的值2.在寄存器上执行++操作3.写回内存
}
pthread_join(id,NULL);
//以下的代码只有main线程
printf("gdata = %d\n",gdata);
exit(0);
}
由以上执行结果可以看出:同一份代码,相同的初始值,几次结果不相同。
可重入函数
引言
函数是一段载入到内存的代码。函数的代码可长可短,执行时间长度也不确定。在多线程中,线程之间是可以进行切换的。函数是一段写好的代码,属于程序公有的代码段。一个进程中有多个线程,每一个线程都可以调用这段函数代码执行。而在多线程环境中,线程的切换是无法预料的,你不知道下一秒是哪个线程在执行,每时每刻的运行环境都不一样,因为线程切换也是变化莫测的。这是操作系统调度进程线程的范围,不是我们能够掌控的。既然我们无法改变进程调度,无法得知线程切换的规律,那就不要依赖线程的切换。我们的程序要做到,无论线程怎么切换,执行的结果都要一致。而我们该如何才能够达到无论线程如何切换,执行结果都是一致的呢?
重入:重复进入!!!!
一个函数在执行的过程中被打断,然后会再被再重头执行一次,执行完后,再回来把刚才没执行完的部分执行完。这就相当于嵌套的执行了。函数是公共代码,这样的执行是允许的。函数的执行可以被打断,打断之后还可以再重头执行,执行完后接着执行刚才没有执行的代码,然后第一次执行的代码(被打断的函数)执行结果还是正确的。也就是说,这个函数执行,无论中间把这个函数再嵌入执行多少遍,怎么嵌入,最终执行完,执行的结果都是正确的,这样的函数就是可重入函数
。
如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的,除了下表列出的函数以外,其他函数都保证是线程安全的。
不保证线程安全的常见函数
strtok()是不保证线程安全的,在多线程程序中,就可能会出现问题,strtok()实现的代码使用了线程间共享的数据。
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
void *fun(void *arg)
{
char buff[] = "1 2 3 4 5 6 7 8"; // fun的栈区
//char *p = strtok(buff, " "); // 不保证线程安全
char *q = NULL;
char *p = strtok_r(buff, " ", &q);
while(p != NULL)
{
printf("fun : %s\n", p);
// p = strtok(NULL, " "); // 不保证线程安全
p = strtok_r(NULL, " ", &q);
sleep(1);
}
}
int main()
{
pthread_t id;
int res = pthread_create(&id, NULL, fun, NULL);
assert(res == 0);
// 并发执行的代码
char buff[] = "a b c d e f g h"; // main线程的栈区
//char *p = strtok(buff, " "); // 不保证线程安全
char *q = NULL;
char *p = strtok_r(buff, " ", &q);
while(p != NULL)
{
printf("main: %s\n", p);
//p = strtok(NULL, " "); // 不保证线程安全
p = strtok_r(NULL, " ", &q);
sleep(1);
}
pthread_join(id, NULL);
exit(0);
}