本章定义函数和库的 MT 安全级别。本章论述以下主题:
线程安全可以避免数据竞争。 不管数据值设置的正确与否,都会出现数据争用的情况,具体取决于多个线程访问和修改数据的顺序。
不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式同步,以确保程序以确定的方式操作。
当某个过程由多个线程同时执行时,如果该过程在逻辑上是正确的,则认为该过程是线程安全的。实际上,一般可分为以下几种安全性级别。
不安全
线程安全,可串行化
线程安全,MT 安全
通过将过程包含在语句中来锁定和解除锁定互斥,可以使不安全过程变成线程安全过程,而且可以进行串行化。示例 6–1 说明了 fputs() 的三个简化实现,最初线程是不安全的。
下面是此例程的可串行化版本,它使用单一互斥来防止过程出现并发执行问题。实际上,单一互斥比通常需要的同步效果更强。当两个线程使用 fputs() 将输出发送到不同文件时,一个线程无需等待另一个线程。只有在共享输出文件时,线程才需要进行同步。
最新版本是 MT 安全的。此版本对每个文件都使用一个锁定,允许两个线程同时指向不同的文件。因此,只要例程是线程安全的,该例程就是 MT 安全的,而且例程的执行不会对性能造成负面影响。
/* 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; }
线程手册页 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 部分。
出于以下原因,特意未将某些函数设为安全的。
设置为 MT 安全的接口对单线程应用程序的性能有负面影响。
库具有不安全的接口。例如,函数可能会返回指向栈中缓冲区的指针。可以对其中的某些函数使用可重复执行的对应函数。可重复执行的函数名称是在原始函数名称后附加 "_r"。
确定名称不以 "_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 安全的。表 6–3 中列出了常用的 MT 安全库。其他的库最终会被修改为 MT 安全的。
表 6–3 部分 MT 安全的库
库 |
注释 |
---|---|
符合 System V Interface Definition, Edition 3, X/Open and ANSI C 的数学库 |
|
TLI 接口、XDR、RPC 客户机和服务器、netdir、netselect 以及 getXXbyYY 接口都不是安全的,但都具有 getXXbyYY_r 形式的线程安全接口 |
|