#基本概念 ##什么是函数的可重入性?
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关 键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
可重入函数:
不为连续的调用持有静态数据。 不返回指向静态数据的指针;所有数据都由函数的调用者提供。 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。 绝不调用任何不可重入函数。
##什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
##函数的可重入性与线程安全之间的关系
可重入的函数不一定是线程安全的; 可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题(可能是线程安全的也可能不是线程安全的); 不可重入的函数一定不是线程安全的;
#进程与线程的区别 ##每个线程独立拥有函数调用栈,共享了进程的资源
- 例如下面这段代码
- 变量存放在函数的调用栈上,每个线程独立的访问自身的函数调用栈,多线程同时运行时得到的结果相互独立,互不干扰。
void add(int a)
{
a++;
printf("%d\n", a);
}
- 再比如下面这段程序,使用了全局变量,而全局变量存放在堆区,同一进程的多个线程共享堆区,都能对这一区域进行修改,这样就会影响程序的运行正确性
- 当线程A执行完a++以后正要执行printf,此时另一个线程B也开始执行a++,所以线程A就会打印出a被自加了两次以后得到的结果。
int a = 0;
void add()
{
a++;
printf("%d\n", a);
}
##可重入函数
- 可重入函数是指两个或者多个线程同时进入一个函数内部执行,而不会发生错误
- 例如上面的那段代码,改一下加个互斥锁
- 当两个线程同时执行这段代码的时候,要求获得互斥锁,即保证了互斥的访问临界区,在对全局变量写操作之前加上互斥锁,使得函数可重入
int a = 0;
int function()
{
pthread_mutex_lock(&gplusplus);
a++;
printf("%d\n", a);
pthread_mutex_unlock(&gplusplus);
}
##线程安全
- 线程安全是指在多线程环境下程序运行能够得到正确的结果
- 如上面的程序所示,保证了函数是可重入的,可是这并不意味这线程安全
- 比如有如下两段代码
- 这两个函数都是可重入函数,但整个代码模块不是线程安全的A函数执行时能写a,B函数执行也能写a
- 要想保证线程安全,两个函数必须申请同一把锁
- 线程安全则一定可重入
- 函数可重入却不一定能保证模块是线程安全的
- 如果仅仅对临界区的写加了互斥锁,这并不能保证真正线程安全,而是要加入读写锁,即对读操作也要加锁而且要保证高效运行。
int a = 0;
int functionA()
{
pthread_mutex_lock(&gplusplus);
a++;
printf("%d\n", a);
pthread_mutex_unlock(&gplusplus);
}
int functionB()
{
pthread_mutex_lock(&gperspers);
a++;
printf("%d\n", a);
pthread_mutex_unlock(&gperspers);
}