设为首页 加入收藏

TOP

6.7.6 线程安全和库
2013-10-07 13:00:15 来源: 作者: 【 】 浏览:55
Tags:6.7.6 线程 安全

6.7.6  线程安全和库

如果不需要采取任何其他动作,库中的函数就可以在某个时刻被多个线程调用,则称该库是线程安全或可重入的。在设计多线程应用程序时,必须小心地确保并发执行的函数是线程安全的。我们已经讨论了如何使用户定义的函数成为线程安全的,但是应用程序经常会调用系统定义的库或第三方提供的库。我们前面讨论了在取消点安全的系统函数,但是这些函数和库中,部分是线程安全的,而部分不是。如果函数不是线程安全的,则意味着该函数:

包含静态变量

访问全局数据

是不可重入的

如果函数包含静态变量,那么这些变量在函数调用之间保持它们的值。函数要求静态变量的值,才能够正确地操作。当多个并发线程调用这个函数时,会产生竞争条件。

如果函数更改一个全局变量,那么多个调用该函数的线程可能每个都尝试更改该全局变量。如果对全局变量的多个并发访问不是同步的,则也可能会产生竞争条件。考虑多个并发线程执行设置errno的函数。对于某些线程,函数失败,errno被设置为错误消息。与此同时,其他线程成功执行。依赖于编译器实现,errno是线程安全的,但假如它不是线程安全的,那么当一个线程检查errno的状态时,它会报告哪个消息?

可重入代码是在使用期间不能够被改变的代码块。可重入代码通过去除对全局变量以及可改动静态数据的引用避免竞争条件。多个并发线程或进程可以共享代码,而其不会发生竞争条件。POSIX标准将多个函数定义为可重入的。可以简单地通过函数名后缀_r来同对应的不可重入的函数进行识别。下面列出了部分可重入函数:

getgrgid_r( )

getgrnam_r( )

getpwuid_r( )

sterror_r( )

strtok_r( )

readdir_r( )

rand_r( )

ttyname_r( )

如果函数访问未经保护的全局变量、包含静态可更改变量、不可重入,则认为该函数不是线程安全的。

1. 使用多线程版本的库和函数

系统库和第三方提供的库可能为它们的标准库提供两个不同的版本,一个版本用于单线程应用程序,另一个版本用于多线程应用程序。只要预计到可能会用于多线程环境,就要链接到库的多线程版本。其他环境不要求链接到库的多线程版本的多线程应用程序,只要求为声明可重入版本的函数定义一些宏。然后这个应用程序就可以作为线程安全的来进行编译。

并不总是可能使用函数的多线程版本。在某些实例中,对于给定的编译器或环境,特定函数的多线程版本不可用。某些函数的接口不能够简单地变成线程安全的。此外,您可能面临增加线程到一个环境中,该环境使用了只打算用在单线程环境中的函数。在这些情况下,可以在程序中使用互斥量来封装所有这样的函数。

例如,某个程序有3个并发执行的线程。其中两个线程ThreadA和ThreadB都并发地执行task1( ),task1( )不是线程安全的。第三个线程,即ThreadC,执行task2( )。为了解决task1( )的问题,方法就是简单地通过一个互斥量来封装ThreadA和ThreadB对task1( )的访问:

  1. ThreadA  
  2. {  
  3.    lock()  
  4.    task1()  
  5.    unlock()  
  6. }  
  7.                 
  8. ThreadB  
  9. {  
  10.    lock()  
  11.    task1()  
  12.    unlock()  
  13. }  
  14.                 
  15. ThreadC  
  16. {  
  17.    task2()  

如果这样做,则任意时刻只有一个线程访问task1( )。但是如果task1( )和task2( )都改动相同的全局变量或静态变量会怎样呢?尽管ThreadA和ThreadB对task1( )使用了互斥量,但ThreadC执行task2( )同它们两者之一是并发的。在这种情况下,会发生竞争条件。为了避免竞争条件,需要对全局数据的访问进行同步。我们将在第7章中讨论这个话题。

2. 线程安全标准输出

为了示范在涉及iostream库时的另一种竞争条件,假定有两个线程ThreadA和ThreadB向标准输出流cout发送输出。cout是类型为ostream的对象,使用插入符(>>)和提取符(<<)来调用cout对象的方法。这些方法是线程安全的吗?如果ThreadA在发送如下的消息:

  1. Global warming is a real problem. 

到stdout,且ThreadB发送如下消息:

  1. Global warming is not a real problem. 

那么输出是否会交错并产生如下的消息?

  1. Global warming is a Global warming is not a real problem real problem. 

在某些情况下,线程安全函数是通过原子(atomic)函数来实现的。原子函数是一旦开始执行就不能够被中断的函数。对于cout,如果插入操作是原子的,那么这种交错不会发生。当您多次调用插入符操作时,它们会像按照串行顺序那样来执行。先显示ThreadA的消息,然后显示ThreadB的消息,或者反之。这是将函数或操作串行化,使之成为线程安全的实例。

这并非令函数线程安全的唯一途径。如果没有不利的影响,函数可能会交错地操作。例如,如果某个方法向未排序的结构中增加或删除元素,而且两个不同的线程调用该方法,那么交错它们的操作将不会有不利的影响。

如果不知道库中的哪些函数是线程安全的,而哪些不是,您有以下3种选择:

所有非线程安全函数的使用限制为单线程使用

不使用任何非线程安全函数

将所有潜在的非线程安全的函数封装到单独的一套同步机制中

为了扩展最后一个选择,您可以为所有将被用在多线程应用程序中的所有非线程安全函数创建接口类。包装器的思想已经在本章前面部分中为系统调用设置取消点时示范过。非线程安全的函数封装在一个接口类中。该类可以同适当的同步对象结合,并可被宿主类(host class)通过继承或组合来使用。这种方法降低了竞争条件的可能性,将在第7章中讨论。然而,首先我们希望讨论第4章引入的thread_object接口类,并对它进行扩展,以封装线程属性对象。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇6.8 扩展线程接口类(1) 下一篇10.4.3 使用PADL第5层的修正的解..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: