线程安全与可重入函数

  1. 定义

    (1).线程安全函数:一般说来,一个函数被称为线程安全的,当它被多个并发线程反复调用时,它会一直产生正确的结果。

    (2).可重入:程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。

    (3).拓展: 

    1).如果一个函数中用到了全局或静态变量,那么它不是线程安全的,也不是可重入的; 

    2).如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题; 

    3).如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。

  2. 联系

    可重入函数是线程安全函数的一个真子集。即可重入函数是线程安全函数,但是反过来,线程安全函数未必是可重入函数。

  3. 区别

    (1)解决问题:

    a.可重入函数要解决的问题是,不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。

    b.线程安全函数要解决的问题是,多个线程调用函数时访问资源冲突。

    (2)确保措施

  a.确保线程安全的措施是:线程安全函数不使用共享数据(全局、静态或堆)或者对共享数据实施同步机制保护。

  b.保障可重入的措施:不共享数据并且不调用不可重入函数。

    (1)不要使用static变量和全局变量,坚持只用局部变量;

    (2)若必须访问全局变量,利用互斥信号量来保护全局变量;

    (3)获取得知哪些系统调用是可重入的,在多任务处理程序中都使用安全的系统调用;

    (4)不调用其它任何不可重入的函数;

    (5)谨慎使用堆栈malloc/new。

  (3)转化

   函数如果使用静态变量,通过加锁后可以转成线程安全函数,但仍然有可能不是可重入的,比如    strtok。strtok是既不可重入的,也不是线程安全的。加锁的strtok不是可重入的,但线程安全。而    strtok_r既是可重入的,也是线程安全的。

  (4)在信号处理函数被调用

 a. 可重入与线程安全的区别体现在能否在信号处理函数中被调用的问题上,可重入函数在信号处理函数中可以被安全调用,因此同时也是异步信号安全函数;而线程安全函数不保证可以在信号处理函数中被安全调用,如果通过设置信号阻塞集合等方法保证一个非可重入函数不被信号中断,那么它也是异步信号安全函数。

值得一提的是POSIX 1003.1的Syste m Interface缺省是线程安全的,但不是异步信号安全的。异步信号安全的需要明确表示,比如fork ()和signal()。

 b. 一个非可重入函数通常(尽管不是所有情况下)由它的外部接口和使用方法即可进行判断。例如:strtok()是非可重入的,因为它在内部存储了被标记分割的字符串;ctime()函数也是非可重入的,它返回一个指向静态数据的指针,而该静态数据在每次调用中都被覆盖重写。

 c. 线程安全只与函数的内部实现有关,而不影响函数的外部接口。在C语言中,局部变量是在栈上分配的。因此,任何未使用静态数据或其他共享资源的函数都是线程安全的。一个线程安全的函数通过加锁的方式来实现多线程对共享数据的安全访问。

4. 函数的线程不安全与不可重入的原因

(1)任何线程不安全问题的根源都是“共享数据”所以,不使用任何共享数据的函数(即:可重入函  数)肯定是线程安全的。

(2)不可重入函数的原因在于:

 a. 已知它们使用静态数据结构
 b. 它们调用malloc和free.
 因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正  在修改此链接表。
 c. 它们是标准IO函数.
 因为标准IO库的很多实现都使用了全局数据结构

(3)线程安全函数不一定是可重入函数,因为即使有线程有共享数据,线程被并发调用的时候也可以使  其结果正确–通过同步操作保证正确性 

  共享数据可以是:

  1. 函数把返回结果放到一个公共的位置

  2. 由调用者传入的线程间共享的指针变量或者引用变量

  3. 函数内部本来就会使用的共享静态变量

4. 补充

(1)常见的不可重入函数有:
printf ——–引用全局变量stdout
malloc ——–全局内存分配表
free    ——–全局内存分配表


(2)不可重入的解决方举例(printf)

例如,程序正在调用printf输出,但是在调用printf时,出现了信号,对应的信号处理函数也有printf语句,就会导致两个printf的输出混杂在一起。
如果是给printf加锁的话,同样是上面的情况就会导致死锁。对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。
屏蔽信号的方法:
1> signal(SIGPIPE, SIG_IGN); //忽略一些信号
2> sigprocmask()
sigprocmask只为单线程定义的
3> pthread_sigmask()
pthread_sigmasks可以在多线程中使用

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/207718.html

(0)
上一篇 2021年12月5日
下一篇 2021年12月5日

相关推荐

发表回复

登录后才能评论