本文出自:BBS水木清华站 作者:Mccartney (coolcat) (2002-01-29 20:32:05)
5. 安全和不安全的接口
本章定义了函数和库的多线程安全等级。
线程安全
多线程接口安全等级
异步安全函数
库的多线程安全等级
5.1线程安全
线程安全是为了避免数据竞争--数据设置的正确性依赖于多个线程修改数据的顺序。
如果不需要共享,则给每个线程分配一个私有的数据拷贝。如果数据必须共享,一定要用同步机制来保证操作的唯一性。
如果一个线程在几个线程同时执行时在逻辑上是正..的。在一个实际的水平上,把安全等级划分为3层比较方便。
· 不安全
· 线程安全--非并行
· 线程安全--多线程安全
一个不安全的过程可以用在操作前加互斥锁,操作后解互斥锁的办法来使操作序列化(即消除并发)。示例5-1首先显示了一个简化的fputs()的非线程安全实现。
接下来是用单互斥锁保护使操作序列化的版本。实际上,使用了比需要的更强的同步。如果两个线程调用fputs()来打印到不同的文件时,其中一个用不着等待另一个--它们可以同时操作。
最后一个版本是多线程安全版。它给每个文件加一个锁,允许两个线程同时指向不同的文件。所以,MT-SAFE(即多线程安全)的函数是线程安全的,并不会使运行性能变坏。
Code Example 5-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)]);
}
5.2多线程接口安全等级
man page(3):库函数用下面的分类来描述一个接口支持多线程到什么程度(这些分类在Intro(3) man page中解释地更为详细)。
Safe 可以被多线程应用程序调用
Safe with exceptions 例外的部分请参见NOTES部分
Unsafe 这个接口只有在应用程序保证一个时刻只有一个线程执行时才
能安全调用
MT-Safe 完全为多线程设计,不但安全,还支持一些并发性
MT-Safe with exceptions 例外的部分请参见NOTES部分
Async-Safe 可以被一个信号控制器安全调用。一个线程在执行Async-Safe函数时被信号中断将不会产生死锁。
有关safe接口请看附录B的表""MT Safety Levels:Library Interfaces."", 它来自man pages(3)。如果一个第三部分的接口不在表内,它就有可能是不 安全的(不包括源兼容库Source Compatibility Library)。检查man page后才 能确定。
在""man pages(2):系统调用""中描述的所有函数,除了vfork(2)外都是MT-Safe的。
一些函数有意地不作成安全,因为如下原因。
对于单线程的应用程序,MT-Safe回在一定程度上降低性能。
函数本身有一个不安全接口。例如,一个函数会返回一个指向堆栈缓冲区的指针。你可以用这些函数""再进入""的对等函数???(原文为
reentrant counterparts)。再进入函数的名字是原函数加""_r""后缀。
-------------------------------------
注意--除非通过查询手册页(man pages),否则无法确定一个不以""_r""结尾的 函数是否MT-safe。非MT-safe的函数一定要有同步机制的保护,或者被限制在 初始线程里。
------------------------------------
*非安全接口的替代(重入 Reentrant)函数
对于大多数非安全接口的函数,都存在一个MT-safe的版本。新的MT-safe函数一般是旧的非安全函数加上""_r""后缀。Solaris系统提供以下的""_r""函数。
Table 5-1 替代函数
asctime_r(3C) ctermid_r(3S) ctime_r(3C)
fgetgrent_r(3C) fgetpwent_r(3C) fgetspent_r(3C)
Gamma_r(3M) getgrgid_r(3C) getgrnam_r(3C)
getlogin_r(3C) getpwnam_r(3C) getpwuid_r(3C)
getgrent_r(3C) gethostbyaddr_r(3N) gethostbyname_r(3N)
gethostent_r(3N) getnetbyaddr_r(3N) getnetbyname_r(3N)
getnetent_r(3N) Getprotobyname_r(3N) getprotobynumber_r(3N)
getprotoent_r(3N) getpwent_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_(3C)r nis_sperror_r(3N) rand_r(3C)
readdir_r(3C) strtok_r(3C) tmpnam_r(3C)
ttyname_r(3C)
5.3异步安全函数
可以被信号控制器安全调用的函数被称为Async-Safe的。POSIX标准定义并 详列了异步安全函数(IEEE Std 1003.1-1990.3.3.1.3(3)(f), page 55)。除 了POSIX异步安全函数外,下列三个函数也是异步安全的。
· sema_post(3T)
· thr_sigsetmask(3T)
· thr_kill(3T)
5.4库的多线程安全等级
所有可能被多线程程序的线程调用的函数都应当是MT-Safe的。
这意味着过程可以同时正确地执行两个操作。所以,每一个被多线程程序使用的接口都应是MT-Safe。
并不是所有的库都是MT-Safe的。通常被使用的MT-Safe的库详列于表5-2中。其他的库也将最终被改写成MT-Safe的。
表5-2 一些MT-Safe库
------------------------------------
库 说明
------------------------------------
lib/libc getXXbyYY接口(例如gethostbyname(3N))是MT-Safe的
lib/libdl_stubs (支持static switch compiling)
lib/libintl
lib/libm 仅当为共享库编译时是MT-Safe的,但与文档库连接时
不是MT-Safe的
lib/libmalloc
lib/libmapmalloc
lib/libnsl 包括TLI接口,XDR,RPC客户方和服务方,netdir和
netselect。 GetXXbyYY是不安全的,但有线程安全版本
GetXXbyYY_r
lib/libresolv 支持因线程而异的错误码
lib/libsocket
lib/libw
lib/nametoaddr
lib/nametoaddr
lib/nsswitch
libX11
libC (不是Solaris系统的部分;可以分开购买)
------------------------------------
*不安全库
如果库中的函数不是MT-Safe的,则只有在一个线程的调用时才是安全的。