线程安全与可重入函数

2017-06-10 13:13 阅读 579 次 评论 2 条

多线程程序处于一个多变的环境当中,可访问的全局变量和堆数据随时可能被其他线程改变。数据同步从根本上说是一个很难的问题,它引出了在普遍的顺序中不会出现的问题,线程安全应运而生。

线程安全

线程安全是多个线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取结束,其他线程才可使用,保证了数据的一致性。

与之对应的则是线程不安全,对数据的访问不提供保护机制,导致多个线程先后更改数据造成数据的不一致问题,这是一个非常严重的问题。

从之前二元信号到生产者消费者模型都存在很严重的线程不安全问题,不过那只是线程安全性问题的一个缩影。下面我们来看一个多线程的i++操作

i++操作我们只需要三步操作:

1)读取i到某个寄存器中。

2)i++。

3)将寄存器中的内容存储回i中。

从实验结果可以看出,两个线程并发执行,程序在用户态与内核态之间不断的切换,两个线程对其进行访问,就会造成数据的不一致问题。

如果你看了i++的汇编代码就会发现,i++操作并不是一条指令,即非原子的。操作系统执行i++的时候可能执行了一半就被调度去执行另一个代码,而单条指令是不会被打断的。

解决方式是:① 将i++操作换成inc指令,可以直接增加一个内存单元值,在Win里,也提供了一套Interlocked API专门处理原子操作。

② 对临界区加锁(二元信号量、互斥锁、多元信号量等均可)。

下面我们使用互斥锁对临界区进行加锁保护:

这样一来,就保证了多线程访问时造成的数据不一致问题,当然,这仅仅是线程安全的冰山一角。

我们能够定义出四个(不相交的)线程不安全函数类

1)不保护共享变量的函数。

比如上述程序中的i++操作。

解决方法: 对临界区加锁,或者使用PV操作来保护共享的变量。

2)保持跨越多个调用的状态的函数。

比如一个伪随机数生成器,当调用srand为rand设置一个种子后,如果多线程调用rand函数,就会造成线程的安全隐患。

解决方法:重写rand函数,使得它不再使用任何static数据,而是依靠调用者在参数中传递状态信息。

3)返回指向静态变量的指针的函数。

比如将一个计算结果放在一个static变量中,然后返回一个指向这个变量的指针。如果多线程调用这些函数,正在被一个线程使用的结构会被另一个线程覆盖掉。

解决方法:① 选择重写函数,使得调用者传递存放结果的变量的地址,消除了所有共享数据。 ② 使用加锁-拷贝(lock-and-copy)技术。将线程不安全函数与互斥锁联系起来,在每一个调用位置,对互斥锁加锁,调用线程不安全函数,将函数返回的结果拷贝到一个私有的存储器位置,然后对互斥锁解锁。

4)调用线程不安全函数的函数。

我们假设函数A安全,函数B不安全情况①:如果函数A调用B,那么A不一定不安全。如果B是第2)累的函数,即依赖于跨越多次调用的状态,那么A线程肯定不安全。解决方法:对函数B进行重写。

情况②:如果B是第1)类或者第3)类。解决方法:需要用互斥锁保护调用位置和任何得到的共享数据,A仍可能是线程安全的。

可重入函数

可重入函数:可以重复进入。这个函数不仅可以被中断,而且除了使用自己栈上的变量以外不依赖于任何环境(包括static)。可以允许有多个函数的副本在运行,由于他们使用的是分离的栈,因此不会互相干扰。

不可重入函数:一重入就出错。由于使用了一些系统资源,比如全局变量区,中断向量表等,如果被中断,是不能再多任务环境下生存的。

很多时候,可重入函数与县城安全被用作同义词,但是他们还是有很明显的区别的,可重入函数仅仅是线程安全函数的一个真子集,如下图所示:

一个函数要想被重入,只有一下两种情况:

1)多个线程同时执行这个函数。

2)函数自身(可能是经过多层调用之后)调用本身。

一个函数之所以可重入,则表明了重入对该函数被会造成任何的不良影响。

一个函数称为可重入的充要条件:

① 不是呀任何(局部)静态或全局的非const变量。

② 不返回任何(局部)静态或全局的非const变量的指针。

③ 仅依赖于调用方提供的参数。

④ 不依赖任何单个资源的锁(mutex等)。

⑤ 不调用任何不可重入的函数。

可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。

可重入函数与线程安全函数的区别

1)可重入函数属于线程安全函数的一个真子集,即可重入函数是线程安全的充分条件。

2)线程安全是多个线程下引起的,但可重入函数可以在只有一个线程的情况下发生。

3)若一个函数中存在全局变量,那么这个函数不是线程安全函数。

4)线程安全函数能够使不同的线程访问同一块地址空间,而可重入函数要求不同的执行流对数据的操作互不影响使结果是相同的。

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:线程安全与可重入函数 | 术与道的分享
分类:操作系统 标签:, , ,
1024do.com导航_术与道导航平台

发表评论


表情

  1. 得一奥
    得一奥 【农民】 @回复

    可重入函数是针对于单线程来说的?那为什么它又是线程安全的充分条件,如果仅针对于单线程来说,那么它与线程安全岂不是没有关系了

  2. 得一奥
    得一奥 【农民】 @回复

    若一个函数中存在全局变量,那么这个函数不是线程安全函数?如果这个全局变量是只读的呢?也不安全吗?如果加了互斥锁也不安全吗?