多线程编程指南

第 6 章 安全和不安全的接口

本章定义函数和库的 MT 安全级别。本章论述以下主题:

线程安全

线程安全可以避免数据竞争。 不管数据值设置的正确与否,都会出现数据争用的情况,具体取决于多个线程访问和修改数据的顺序。

不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式同步,以确保程序以确定的方式操作。

当某个过程由多个线程同时执行时,如果该过程在逻辑上是正确的,则认为该过程是线程安全的。实际上,一般可分为以下几种安全性级别。

通过将过程包含在语句中来锁定和解除锁定互斥,可以使不安全过程变成线程安全过程,而且可以进行串行化。示例 6–1 说明了 fputs() 的三个简化实现,最初线程是不安全的。

下面是此例程的可串行化版本,它使用单一互斥来防止过程出现并发执行问题。实际上,单一互斥比通常需要的同步效果更强。当两个线程使用 fputs() 将输出发送到不同文件时,一个线程无需等待另一个线程。只有在共享输出文件时,线程才需要进行同步。

最新版本是 MT 安全的。此版本对每个文件都使用一个锁定,允许两个线程同时指向不同的文件。因此,只要例程是线程安全的,该例程就是 MT 安全的,而且例程的执行不会对性能造成负面影响。


示例 6–1 线程安全程度

/* not thread-safe */

fputs(const char *s, FILE *stream) {

    char *p;

    for (p=s; *p; p++)

        putc((int)*p, stream);

}



/* serializable */

fputs(const char *s, FILE *stream) {

    static mutex_t mut;

    char *p;

    mutex_lock(&m);

    for (p=s; *p; p++)

        putc((int)*p, stream);



    mutex_unlock(&m);

}



/* MT-Safe */

mutex_t m[NFILE];

fputs(const char *s, FILE *stream) {

    static mutex_t mut;

    char *p;

    mutex_lock(&m[fileno(stream)]);

    for (p=s; *p; p++)

        putc((int)*p, stream);

    mutex_unlock(&m[fileno(stream)]0;

}

MT 接口安全级别

线程手册页 man(3C) 使用表 6–1 中列出的安全级别类别来描述接口对线程的支持程度。这些类别在 Intro(3) 手册页中进行了完整说明。

表 6–1 接口安全级别

类别

说明

安全 

可以从多线程应用程序中调用此代码 

安全(包含异常) 

有关异常的说明,请参见对应手册页的 NOTES 部分。 

不安全 

此接口与多线程应用程序结合使用时是不安全的,除非应用程序一次仅安排在库中执行一个线程。  

MT 安全 

此接口已完全做好准备,可以执行多线程访问。此接口是安全的,而且支持一定的并发性。

MT 安全(包含异常) 

有关异常的列表,请参见《man pages section 3: Basic Library Functions》中对应页中的 NOTES 部分。

异步信号安全 

可以从信号处理程序中安全地调用此例程。执行异步信号安全例程的线程在被信号中断时,不会自行死锁。  

Fork1–安全 

每次调用 Solaris fork1(2) 或 POSIX fork(2) 时,此接口都会释放持有的锁定。

有关库例程的安全级别,请参见参考手册页的第 3 部分。

出于以下原因,特意未将某些函数设为安全的。


注意 – 注意 –

确定名称不以 "_r" 结尾的函数是否是 MT 安全的唯一方法就是查看该函数的手册页。必须使用同步设备或通过限制初始线程来保护对标识为非 MT 安全的函数的使用。


不安全接口的可重复执行函数

对于包含不安全接口的大多数函数而言,存在例程的 MT 安全版本。新的 MT 安全例程的名称始终为原有不安全例程的名称附加 "_r" 后的形式。Solaris 环境中提供表 6–2 "_r" 例程。

表 6–2 可重复执行函数

asctime_r(3c)

gethostbyname_r(3n)

getservbyname_r(3n)

ctermid_r(3s)

gethostent_r(3n)

getservbyport_r(3n)

ctime_r(3c)

getlogin_r(3c)

getservent_r(3n)

fgetgrent_r(3c)

getnetbyaddr_r(3n)

getspent_r(3c)

fgetpwent_r(3c)

getnetbyname_r(3n)

getspnam_r(3c)

fgetspent_r(3c)

getnetent_r(3n)

gmtime_r(3c)

gamma_r(3m)

getnetgrent_r(3n)

lgamma_r(3m)

getauclassent_r(3)

getprotobyname_r(3n)

localtime_r(3c)

getauclassnam_r(3)

getprotobynumber_r(3n)

nis_sperror_r(3n)

getauevent_r(3)

getprotoent_r(3n)

rand_r(3c)

getauevnam_r(3)

getpwent_r(3c)

readdir_r(3c)

getauevnum_r(3)

getpwnam_r(3c)

strtok_r(3c)

getgrent_r(3c)

getpwuid_r(3c)

tmpnam_r(3s)

getgrgid_r(3c)

getrpcbyname_r(3n)

ttyname_r(3c)

getgrnam_r(3c)

getrpcbynumber_r(3n)

 

gethostbyaddr_r(3n)

getrpcent_r(3n)

 

异步信号安全函数

可以从信号处理程序中安全调用的函数就是异步信号安全函数。POSIX 标准定义并列出了异步信号安全函数(IEEE Std 1003.1-1990, 3.3.1.3 (3)(f),第 55 页)。除 POSIX 异步信号安全函数外,Solaris 线程接口中的以下函数也是异步信号安全函数:

库的 MT 安全级别

所有可能由线程从多线程程序中调用的例程都应该是 MT 安全的。因此,例程的两项或多项激活操作必须能够同时正确执行。这样,多线程程序使用的每个库接口都必须是 MT 安全级别。

目前,并非所有库都是 MT 安全的。表 6–3 中列出了常用的 MT 安全库。其他的库最终会被修改为 MT 安全的。

表 6–3 部分 MT 安全的库

库 

注释 

lib/libc

不安全的接口具有 *_r 形式的线程安全接口,通常包含不同的语义。

lib/libdl_stubs

支持静态切换编译

lib/libintl

国际化库

lib/libm

符合 System V Interface Definition, Edition 3, X/Open and ANSI C 的数学库

lib/libmalloc

空间有效内存分配库,请参见 malloc(3X)

lib/libmapmalloc

基于 mmap 的备选内存分配库,请参见 mapmalloc(3X)

lib/libnsl

TLI 接口、XDR、RPC 客户机和服务器、netdirnetselect 以及 getXXbyYY 接口都不是安全的,但都具有 getXXbyYY_r 形式的线程安全接口

lib/libresolv

线程特定 errno 支持

lib/libsocket

用于执行网络连接的套接字库

lib/libw

支持多字节语言环境的宽字符和宽字符串函数

lib/straddr

名称到地址的网络转换库

lib/libX11

X11 Windows 库例程

lib/libC

C++ 运行时共享对象

不安全库

只有在单线程调用时,多进程程序才能安全地调用库中无法保证是 MT 安全级别的例程。