全部博文(842)
分类: 系统运维
2012-05-14 14:30:21
我们在10.6节讨论过再入函数和信号处理机。线程和信号处理机一样,当考虑再入时。有了信号处理机和线程,多线程控制可能潜在地同时调用相同的函数。
如 果一个函数可以同时被多个线程同时调用,那么我们说这个函数是线程安全(thread-safe)的。所有在SUS定义的函数都被保证为线程安全的,除了 下表列出的。此外,ctermid和tmpnam函数不被保证为线程安全的,如果它们被传入空指针。相似地,wcrtomb和wcsrtombs也不保证 为线程安全的,当它们的mbstate_t参数被传入空指针。
asctime | ecvt | gethostent | getutxline | putc_unlocked |
basename | encrypt | getlogin | gmtime | puchar_unlocked |
catgets | endgrent | getnetbyaddr | hcreate | putenv |
crypt | endpwent | getnetbyname | hdestroy | pututxline |
ctime | endutxent | getnetent | hsearch | rand |
dbm_clearerr | fcvt | getopt | inet_ntoa | readdir |
dbm_close | ftw | getprotobyname | l64a | setenv |
dbm_delete | gcvt | getprotobynumber | lgamma | setgrent |
dbm_error | getc_unlocked | getprotoent | lgammaf | setkey |
dbm_fetch | getchar_unlocked | getpwent | lgammal | setpwent |
dbm_firstkey | getdate | getpwnam | localeconv | setutxent |
dbm_nextkey | getenv | getpwuid | localtime | strerror |
dbm_open | getgrent | getservbyname | lrand48 | strtok |
dbm_store | getgrgid | getservbyport | mrand48 | ttyname |
dirname | getgrnam | getservent | ntfw | unsetenv |
dlerror | gethostbyaddr | getutxent | nl_langinfo | wcstombs |
drand48 | gethostbyname | getutxid | ptsname | wctomb |
支
持线程安全函数的实现会在
当支持线程安全函数特性时,一个实现为一些不是线程安全的POISX.1函数提供一个替代的、线程安全的版本。下表列出了这些函数的线 程安全版本。许多函数不是线程安全的,因为它们返回一个存储在静态内存缓冲的数据。通过改变它们的接口来需要调用者提供它自己的缓冲,它们被实现为线程安 全的。
acstime_r | gmtime_r |
ctime_r | localtime_r |
getgrgid_r | rand_r |
getgrnam_r | readdir_r |
getlogin_r | strerror_r |
getpwnam_r | strtok_r |
getpwuid_r | ttyname_r |
如 果函数关于多线程是可再入的,那么我们说它是线程安全的。然而,这并不是告诉我们这个函数关于信号是否是可再入的。我们说从异步信号处理机再入时是安全的 的函数是异步信号安全的(async-signal safe)。我们在10.6节讨论再入函数时看到了异步信号安全函数。
除了上表列出的 函数,POSIX.1提供了一种以线程安全方式管理FILE对象的方法。你可以使用flockfile和ftrylockfile来得到给定FILE对象 相关的锁。这个锁是递归的:当你握住它时,你可以再次申请它,而不会造成死锁。尽管锁的精确的实现没有规定,但是所有操作FILE对象的标准I/O例程都 需要表现得好像它们内部调用flockfile和funlockfile。
尽管标准I/O例程从它们自己内部的数据结构来看,可能被实现为线程安全的,但是暴露这个锁给应用是仍然有用的。这允许应用组合多个标准I/O函数为一个原子序列。当然,当处理多个FILE对象时,你需要知道潜在的死锁并小心得排序你的锁。
如果标准I/O例程申请它们自己的锁,那么我们在执行一次一字符I/O时会进入严重的性能退化。这种情况下,我们为每个字符的读或写申请和释放锁。为了避免这个开销,基于字符的标准I/O例程的无锁版本可用。
这四个函数不应该被调用,除非被flockfile或ftrylockfile调用和funlockfile调用包围。否则,不可预期的结果会发生(也就是说,这种类型的问题由多线程控制的未同步的数据访问导致)。
一旦你锁住FILE对象,你可以在释放这个锁之前多次调用这些函数。这把锁的开销平摊到读写数据的量上。
下面的例子展示了getenv(7.9节)的一个可能的实现。这个版本是不可再入的。如果两个线程同时调用它,那么它们将看到不一致的结果,因为返回的字符串被存储在单个静态缓冲里,它被所有调用getenv的线程共享。
我们可以用读写锁来允许多个并发调用getenv_r, 但是加上的并发很可能不会提高我们程序的性能很多,由于两个原因。第一,环境列表通常不是很长,所以我们不必在浏览这个列表时不必握住这个互斥体太久。第 二,getenv和putenv的调用是不频繁的,所以如果我们提高它们的性能,我们对程序的整体性能也不会有很大影响。
如果我们使 getenv_r线程安全,那么这不表示它是关于信号处理机可再入的。如果我们使用一个非递归的互斥体,那么我们冒着一个线程如果从一个信号处理机里调用 getenv_r会死锁它自己的风险。如果信号处理机在线程执行getenv_r时中断了它,我们将正锁着env_mutex,所以另一个锁住它的尝试会 阻塞,导致线程死锁。因而,我们必须使用一个递归互斥体来防止其它线程在我们查看数据结构时改变它们,也防止从信号处理机而来的死锁。问题是 pthread函数不被保证是异步信号安全的,所以我们不能使用它们来使另一个函数异步信号安全。