1. 线程安全
当两个线程同时访问到同一个临界资源的时候,如果对临界资源的操作不是原子的就会产生冲突,使得结果并不如最终预期的那样。比如下面的代码:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#define NLOOP 5000
static int g_val = 0;
void *read_write_mem(void *_val)
{
int val = 0;
int i = 0;
for(; i < NLOOP; i++)
{
val = g_val;
printf("pthread id is: %x, count is: %d\n", (unsigned long)pthread_self(), g_val);
g_val = val + 1;
}
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, read_write_mem, NULL);
pthread_create(&tid2, NULL, read_write_mem, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("count final val is: %d\n", g_val);
}
运行结果为:
由运行结果可以看出,结果并不是我们预期的结果10000,而且每一次运行的结果都不一样;因此,这样的代码不是线程安全的。
线程安全是指当多个线程访问同一个区域的时候其最终的结果是可预期的,并不会因为产生冲突或者异常中断再次恢复而使结果不可预期。
2. 可重入函数与不可重入函数
函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。当函数访问一个全局的变量或者参数时,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入 (Reentrant) 函数。
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。
如果一个函数符合以下条件之一则是不可重入的:
(1)调用了malloc或free,因为malloc是用全局链表来管理堆的。
(2)调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使⽤用全局数据结构。
(3)函数体内使用了静态的数据结构。
3. 可重入函数与线程安全的区别与联系
线程安全是在多个线程的情况下引发的,而可重入函数可以在只有一个线程的情况下来说;
线程安全不一定是可重入的,而可重入函数则一定是线程安全的;
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的;
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果重入函数的话若锁还未释放则会产生死锁,因此不是可重入的;
如果一个函数当中的数据全是自身栈空间的,那么这个函数既是线程安全也是可重入的;
线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。
线程安全不一定是可重入的,而可重入函数则一定是线程安全的;
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的;
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果重入函数的话若锁还未释放则会产生死锁,因此不是可重入的;
如果一个函数当中的数据全是自身栈空间的,那么这个函数既是线程安全也是可重入的;
线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。