线程安全
基本定义
线程安全:简单来说线程安全就是多个线程并发同一段代码时,不会出现不同的结果,我们就可以说该线程是安全的;
线程不安全:说完了线程安全,线程不安全的问题就很好解释,如果多线程并发执行时会产生不同的结果,则该线程就是不安全的。
线程安全产生的原因:大多是因为对全局变量和静态变量的操作
常见的线程
不安全的函数
(1)不保护共享变量的函数
(2)函数状态随着被调用,状态发生变化的函数
(3)返回指向静态变量指针的函数
(4)调用线程不安全函数的函数
常见的线程
安全的情况
(1)每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的;
(2)类或者接口对于线程来说都是原子操作;
(3)多个线程之间的切换不会导致该接口的执行结果存在二义性;
执行代码:
#include<stdio.h>
#include<pthread.h>
int value=0;
void* func(void* arg){
int i=0;
while(i<10000){
int tmp=value;
value=i;
printf("value is %d\n",value);
value=tmp+1;
i++;
}
}
int main()
{
pthread_t id1,id2;
pthread_create(&id1,NULL,func,NULL);
pthread_create(&id2,NULL,func,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("value is %d\n",value);
return 0;
}
可重入函数
基本定义
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的进程已经再次调用(执行流之间的相互嵌套执行);
可重入:多个执行流反复执行一个代码,其结果不会发生改变,通常访问的都是各自的私有栈资源;
不可重入:多个执行流反复执行一段代码时,其结果会发生改变;
可重入函数:当一个执行流因为异常或者被内核切换而中断正在执行的函数而转为另外一个执行流时,当后者的执行流对同一个函数的操作并不影响前一个执行流恢复后执行函数产生的结果;
不可重入函数:当程序运行到某一个函数的时候,可能因为硬件中断或者异常而使得在用户正在执行的代码暂时终端转而进入你内核,这个时候如有一个信号需要被处理,而处理的这个信号的时候又会重新调用刚才中断的函数,如果函数内部有一个全局变量需要被操作,那么,当信号处理完成之后重新返回用户态恢复中断函数的上下文再次继续执行的时候,对同一个全局变量的操作结果可能就会发生改变而并不如我们预期的那样,这样的函数被称为不可重入函数。例如在进行链表的插入时,插入函数访问一个全局链表,有可能因为重入而造成错乱。
可重入函数
满足条件
(1)不使用全局变量或静态变量;
(2)不使用用malloc或者new开辟出的空间;
(3)不调用不可重入函数;
(4)不返回静态或全局数据,所有数据都有函数的调用者提供;
(5)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据;
不可重入函数符合以下条件之一
(1)调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
(2)调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局
数据结构。
(3)可重入体内使用了静态的数据结构。
可重入函数
分类
(1)
显式可重入函数
如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。
(2)
隐式可重入函数
可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。
可重入函数可以有多余一个任务并发使用,而不必担心数据错误,相反,不可重入函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在 代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据,可重入函数要么使用本地变量,要么在使用全局变量时保护自己 的数据。
执行代码:
#include<stdio.h>
#include<signal.h>
int value=0;
void fun(){
int i=0;
while(i++<5){
value++;
printf("value is %d\n",value);
sleep(1);
}
}
int main()
{
signal(2,fun);
fun();
printf("the value is %d\n",value);
return 0;
}