Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1467134
  • 博文数量: 842
  • 博客积分: 12411
  • 博客等级: 上将
  • 技术积分: 5772
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-14 14:43
文章分类

全部博文(842)

文章存档

2013年(157)

2012年(685)

分类: 系统运维

2012-05-14 14:30:21



我们在10.6节讨论过再入函数和信号处理机。线程和信号处理机一样,当考虑再入时。有了信号处理机和线程,多线程控制可能潜在地同时调用相同的函数。


如 果一个函数可以同时被多个线程同时调用,那么我们说这个函数是线程安全(thread-safe)的。所有在SUS定义的函数都被保证为线程安全的,除了 下表列出的。此外,ctermid和tmpnam函数不被保证为线程安全的,如果它们被传入空指针。相似地,wcrtomb和wcsrtombs也不保证 为线程安全的,当它们的mbstate_t参数被传入空指针。


POSIX.1不保证是线程安全的函数
asctimeecvtgethostentgetutxlineputc_unlocked
basenameencryptgetlogingmtimepuchar_unlocked
catgetsendgrentgetnetbyaddrhcreateputenv
cryptendpwentgetnetbynamehdestroypututxline
ctimeendutxentgetnetenthsearchrand
dbm_clearerrfcvtgetoptinet_ntoareaddir
dbm_closeftwgetprotobynamel64asetenv
dbm_deletegcvtgetprotobynumberlgammasetgrent
dbm_errorgetc_unlockedgetprotoentlgammafsetkey
dbm_fetchgetchar_unlockedgetpwentlgammalsetpwent
dbm_firstkeygetdategetpwnamlocaleconvsetutxent
dbm_nextkeygetenvgetpwuidlocaltimestrerror
dbm_opengetgrentgetservbynamelrand48strtok
dbm_storegetgrgidgetservbyportmrand48ttyname
dirnamegetgrnamgetserventntfwunsetenv
dlerrorgethostbyaddrgetutxentnl_langinfowcstombs
drand48gethostbynamegetutxidptsnamewctomb


支 持线程安全函数的实现会在里定义_POSIX_THREAD_SAFE_FUNCTIONS符号。应用也可以使用 _SC_THREAD_SAFE_FUNCTIONS参数的sysconf函数来在运行时检查线程安全函数的支持。所有XSI实现都需要支持线程安全函 数。


当支持线程安全函数特性时,一个实现为一些不是线程安全的POISX.1函数提供一个替代的、线程安全的版本。下表列出了这些函数的线 程安全版本。许多函数不是线程安全的,因为它们返回一个存储在静态内存缓冲的数据。通过改变它们的接口来需要调用者提供它自己的缓冲,它们被实现为线程安 全的。


替代的线程安全函数
acstime_rgmtime_r
ctime_rlocaltime_r
getgrgid_rrand_r
getgrnam_rreaddir_r
getlogin_rstrerror_r
getpwnam_rstrtok_r
getpwuid_rttyname_r

上表列出的函数命名和非线程安全的版本一样,除了有个_r后缀,表示这些版本是可再入的。

如 果函数关于多线程是可再入的,那么我们说它是线程安全的。然而,这并不是告诉我们这个函数关于信号是否是可再入的。我们说从异步信号处理机再入时是安全的 的函数是异步信号安全的(async-signal safe)。我们在10.6节讨论再入函数时看到了异步信号安全函数。


除了上表列出的 函数,POSIX.1提供了一种以线程安全方式管理FILE对象的方法。你可以使用flockfile和ftrylockfile来得到给定FILE对象 相关的锁。这个锁是递归的:当你握住它时,你可以再次申请它,而不会造成死锁。尽管锁的精确的实现没有规定,但是所有操作FILE对象的标准I/O例程都 需要表现得好像它们内部调用flockfile和funlockfile。



  1. #include <stdio.h>

  2. int ftrylockfile(FILE *fp);

  3. 成功返回0,不能得到锁返回非0.

  4. void flockfile(FILE *fp);

  5. void funlockfile(FILE *fp);


尽管标准I/O例程从它们自己内部的数据结构来看,可能被实现为线程安全的,但是暴露这个锁给应用是仍然有用的。这允许应用组合多个标准I/O函数为一个原子序列。当然,当处理多个FILE对象时,你需要知道潜在的死锁并小心得排序你的锁。


如果标准I/O例程申请它们自己的锁,那么我们在执行一次一字符I/O时会进入严重的性能退化。这种情况下,我们为每个字符的读或写申请和释放锁。为了避免这个开销,基于字符的标准I/O例程的无锁版本可用。



  1. #include <stdio.h>

  2. int getchar_unlocked(void);

  3. int getc_unlocked(FILE *fp);

  4. 两者成功返回下个字符,文件尾或错误返回EOF。

  5. int putchar_unlocked(int c);

  6. int putc_unlocked(int c, FILE *fp);

  7. 两者成功返回c,错误返回EOF。


这四个函数不应该被调用,除非被flockfile或ftrylockfile调用和funlockfile调用包围。否则,不可预期的结果会发生(也就是说,这种类型的问题由多线程控制的未同步的数据访问导致)。


一旦你锁住FILE对象,你可以在释放这个锁之前多次调用这些函数。这把锁的开销平摊到读写数据的量上。


下面的例子展示了getenv(7.9节)的一个可能的实现。这个版本是不可再入的。如果两个线程同时调用它,那么它们将看到不一致的结果,因为返回的字符串被存储在单个静态缓冲里,它被所有调用getenv的线程共享。



  1. #define ARG_MAX /* without it linux won't include "ARG_MAX" */
  2. #include <limits.h>
  3. #include <string.h>

  4. static char envbuf[ARG_MAX];

  5. extern char **environ;

  6. char *
  7. getenv(const char *name)
  8. {
  9.     int i, len;
  10.     
  11.     len = strlen(name);
  12.     for (i = 0; environ[i] != NULL; i++) {
  13.         if ((strncmp(name, environ[i], len) == 0) &&
  14.             (environ[i][len] == '=')) {
  15.                 strcpy(envbuf, &environ[i][len+1]);
  16.                 return(envbuf);
  17.         }
  18.     }
  19.     return(NULL);
  20. }

我们在下面的代码展示一个可再入的版本。这个版本称为getenv_r。它使用pthread_once函数(12.6节)来保证thread_init函数每个进程只调用一次。


  1. #include <string.h>
  2. #include <errno.h>
  3. #include <pthread.h>
  4. #include <stdlib.h>

  5. extern char **environ;

  6. pthread_mutex_t env_mutex;
  7. static pthread_once_t init_done = PTHREAD_ONCE_INIT;

  8. static void
  9. thread_init(void)
  10. {
  11.     pthread_mutexattr_t attr;

  12.     pthread_mutexattr_init(&attr);
  13.     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  14.     pthread_mutex_init(&env_mutex, &attr);
  15.     pthread_mutexattr_destroy(&attr);
  16. }

  17. int
  18. getenv_r(const char *name, char *buf, int buflen)
  19. {
  20.     int i, len, olen;

  21.     pthread_once(&init_done, thread_init);
  22.     len = strlen(name);
  23.     pthread_mutex_lock(&env_mutex);
  24.     for (i = 0; environ[i] != NULL; i++) {
  25.         if ((strncmp(name, environ[i], len) == 0) &&
  26.             (environ[i][len] == '=')) {
  27.                 olen = strlen(&environ[i][len+1]);
  28.                 if (olen >= buflen) {
  29.                     pthread_mutex_unlock(&env_mutex);
  30.                     return(ENOSPC);
  31.                 }
  32.                 strcpy(buf, &environ[i][len+1]);
  33.                 pthread_mutex_unlock(&env_mutex);
  34.                 return(0);
  35.         }
  36.     }
  37.     pthread_mutex_unlock(&env_mutex);
  38.     return(ENOENT);
  39. }


 为了让getenv_r可再入,我们改变了接口,以便调用者必须提供它自己的缓冲。因而,每个线程可以用不同的缓冲来避免干涉其它线程的。然 而,注意这并不足以让getenv_r为线程安全的。为了让getenv_r线程安全,我们需要作防止当我们查找请求字符串时环境改变的保护措施。我们可 以使用一个互斥体来序列化getenv_r和putenv对环境列表的访问。

我们可以用读写锁来允许多个并发调用getenv_r, 但是加上的并发很可能不会提高我们程序的性能很多,由于两个原因。第一,环境列表通常不是很长,所以我们不必在浏览这个列表时不必握住这个互斥体太久。第 二,getenv和putenv的调用是不频繁的,所以如果我们提高它们的性能,我们对程序的整体性能也不会有很大影响。


如果我们使 getenv_r线程安全,那么这不表示它是关于信号处理机可再入的。如果我们使用一个非递归的互斥体,那么我们冒着一个线程如果从一个信号处理机里调用 getenv_r会死锁它自己的风险。如果信号处理机在线程执行getenv_r时中断了它,我们将正锁着env_mutex,所以另一个锁住它的尝试会 阻塞,导致线程死锁。因而,我们必须使用一个递归互斥体来防止其它线程在我们查看数据结构时改变它们,也防止从信号处理机而来的死锁。问题是 pthread函数不被保证是异步信号安全的,所以我们不能使用它们来使另一个函数异步信号安全。


阅读(509) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~