多线程编程学习6-安全和不安全接口
时间:2010-09-19 来源:soqsoq
安全和不安全的接口
本章定义函数和库的 MT 安全级别。本章论述以下主题:
■ 第 165 页中的 “线程安全”
■ 第 167 页中的 “MT 接口安全级别”
■ 第 168 页中的 “异步信号安全函数”
■ 第 169 页中的 “库的 MT 安全级别”
线程安全可以避免数据竞争。 不管数据值设置的正确与否,都会出现数据争用的情况,具
体取决于多个线程访问和修改数据的顺序。
不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式
同步,以确保程序以确定的方式操作。
当某个过程由多个线程同时执行时,如果该过程在逻辑上是正确的,则认为该过程是线程
安全的。实际上,一般可分为以下几种安全性级别。
■ 不安全
■ 线程安全,可串行化
■ 线程安全,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 LibraryFunctions》中对应页中的 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)
ctermid_r(3s)
ctime_r(3c)
fgetgrent_r(3c)
fgetpwent_r(3c)
fgetspent_r(3c)
gamma_r(3m)
getauclassent_r(3)
getauclassnam_r(3)
getauevent_r(3)
getauevnam_r(3)
getauevnum_r(3)
getgrent_r(3c)
getgrgid_r(3c)
getgrnam_r(3c)
gethostbyaddr_r(3n)
gethostbyname_r(3n)
gethostent_r(3n)
getlogin_r(3c)
getnetbyaddr_r(3n)
getnetbyname_r(3n)
getnetent_r(3n)
getnetgrent_r(3n)
getprotobyname_r(3n)
getprotobynumber_r(3n)
getprotoent_r(3n)
getpwent_r(3c)
getpwnam_r(3c)
getpwuid_r(3c)
getrpcbyname_r(3n)
getrpcbynumber_r(3n)
getrpcent_r(3n)
getservbyname_r(3n)
getservbyport_r(3n)
getservent_r(3n)
getspent_r(3c)
getspnam_r(3c)
gmtime_r(3c)
lgamma_r(3m)
localtime_r(3c)
nis_sperror_r(3n)
rand_r(3c)
readdir_r(3c)
strtok_r(3c)
tmpnam_r(3s)
ttyname_r(3c)
可以从信号处理程序中安全调用的函数就是异步信号安全函数。POSIX 标准定义并列出了
异步信号安全函数(IEEE Std 1003.1-1990, 3.3.1.3 (3)(f),第 55 页)。除 POSIX 异步信号安全
函数外,Solaris 线程接口中的以下函数也是异步信号安全函数:
■ sema_post(3C)
■ thr_sigsetmask(3C),类似于 pthread_sigmask(3C)
■ thr_kill(3C),类似于 pthread_kill(3C)
库的 MT 安全级别
所有可能由线程从多线程程序中调用的例程都应该是 MT 安全的。因此,例程的两项或多
项激活操作必须能够同时正确执行。这样,多线程程序使用的每个库接口都必须是 MT 安
全级别。
目前,并非所有库都是 MT 安全的。表 6–3 中列出了常用的 MT 安全库。其他的库最终会被修改为 MT 安全的。
表 6–3 部分 MT 安全的库
库 |
注释
|
lib/libc |
不安全的接口具有 *_r 形式的线程安全接口,通常包含不同的语义。 |
lib/libdl_stubs
|
支持静态切换编译 |
lib/libintl
|
国际化库 |
lib/libm
|
符合 System V Interface De?nition, Edition 3, X/Open and ANSI C 的数学库 |
lib/libmalloc
|
空间有效内存分配库,请参见 malloc(3X)
|
lib/libmapmalloc
|
基于 mmap 的备选内存分配库,请参见 mapmalloc(3X)
|
lib/libnsl
|
TLI 接口、XDR、RPC 客户机和服务器、netdir、netselect 以及 getXXbyYY 接口都不是安全的,但都具有 getXXbyYY_r 形式的线程安全接口 |
lib/libresolv
|
线程特定 errno 支持
|
lib/libsocket
|
用于执行网络连接的套接字库 |
lib/libw
|
支持多字节语言环境的宽字符和宽字符串函数 |
lib/straddr
|
名称到地址的网络转换库 |
lib/libX11
|
X11 Windows 库例程 |
lib/libC
|
C++ 运行时共享对象
|
不安全库
只有在单线程调用时,多进程程序才能安全地调用库中无法保证是 MT 安全级别的例程。