UNIX上的C++程序设计守则( 信号和线程 下)

原文:http://d.hatena.ne.jp/yupo5656/20040712/p1

翻译:http://www.cppblog.com/lymons/archive/2008/06/01/51838.html

准则 4: 请不要做线程的异步撤消的设计

l        线程的异步撤销是指:某个线程的执行立刻 被其他线程给强制终止了

l        请不要单单为了让“ 设计更简单” 或者“ 看起了更简单” 而使用线程的异步撤消

咋一看还是挺简单的。但是搞不好可能会引起各种各样的问题。请不要在不能把握问题的实质就做出使用线程的异步撤消的设计!

在pthread 的规格说明中,允许一个线程可以强制中断某个线程的执行。这就是所说的异步撤消。

线程的撤消有下面的两种方式。

  • 方式1 : 异步撤消(PTHREAD_CANCEL_ASYNCHRONOUS)
    • 撤销动作是马上进行的
  • 方式2 : 延迟撤销(PTHREAD_CANCEL_DEFERRED)
    • 撤消动作,是让线程的处理一直被延迟到撤消点才会去执行

还有,到底是用哪种撤消方式,不是撤消者,而是被撤销者能够决定的 *1 。另外,在被撤销者也能够选择完全禁止撤消的这种方式 *2 

会造成什么问题呢

那么,让我看看乱用线程的异步撤消会引起什么问题呢。看过准则3 的人可能会知道,在下面的脚本里,被撤销线程以外的任意一个线程会被死锁。

1.    线程1 中调用malloc 函数正在做内存分配的过程中,线程2 异步撤消了线程1 的处理

2.    线程1 马上被撤销,但是malloc 函数中的互斥锁就没有线程去解除了

3.    后面的任意一个线程如果再次调用malloc 函数的话就会马上导致该线程死锁

在这个例子中使用了malloc 函数,但是其他的危险函数还有很多。

反之,即使做了异步撤消也没有问题的函数也有少数存在的、我们把它们叫做「async-cancel safe 函数」或者「异步撤消安全函数」。在一些商用UNIX *3 中、OS 提供的api 函数的文档说明中有async-cancel safety 的记载、但是在Linux(glibc) 里就很遗憾,几乎没有相关的说明。

在这儿,参看规格(SUSv3) 的话,会发现,、描述异步撤消安全的函数只有3 个 

  1. pthread_cancel
  2. pthread_setcancelstate
  3. pthread_setcanceltype

而且、里面还有"No other functions are required to be async-cancel-safe" 这样的记载。因此,Linux 的场合,如果在文档里没有记载成async-cancel safety 的函数,我们还是把它假定成不安全的函数为好!

如何避免这些问题呢

在多线程编程中为了安全的使用异步撤消处理、有没有回避死锁的方法呢?我们试着想了几个。他们与准则3 里的线程+fork 的场合的回避策很相似。

回避方法1: 被撤销线程中,只能使用异步撤消安全函数

首先,被撤销线程中,只能使用异步撤消安全函数。但是这个方法

  • 在规格说明中只有3 个异步撤消安全的函数
  • 这些以外的函数是不是异步撤消安全( 商用UNIX) 、因为没有说明文档我们不清楚(Linux)

中有以上的两点,所以这个回避方法几乎不现实。

回避方法2: 被撤销线程中,在做非异步撤消安全处理的过程中,再把撤消方式设置成「延迟」或者是「禁止」

第二个是,被撤销线程在做非异步撤消安全处理的过程中,把撤消方式再设定成「延迟」或者「禁止」。对于这个方法

  • 就像方法1 写的那样、要把我那个函数是异步撤消安全的一时还是挺麻烦的
  • 在任意的场所并不能保证撤消动作会被马上执行
    • 例如,再设定成「延迟」后的一段时间内如果撤消发生时、某个正在阻塞的I/O 函数是否能够被解除阻塞还是挺微妙的
    • 如果设定成撤消禁止的话,则撤消会被屏蔽掉

有上面样的问题、会导致「一精心设计撤消方式的替换,从一开始就使用延迟撤消还不够好」这样的结果。所以这几乎是不好的一个回避策 。

回避方法3 : 使用pthread_cleanup_push 函数,登录异步撤消时的线程数据清除的回调函数

第三种则是,用pthread_cleanup_push 函数、登录一个在异步撤消发生时的数据清除的回调函数。这和在准则3 中介绍的pthread_atfork 函数有点儿类似。用这个函数登录的回调函数来清除线程的数据和锁,就可以回避死锁了。

... 但是,pthread_cleanup_push 函数登录的回调函数,在「延迟撤消」的场合是不能被调用的。因此、这个回避方法对于异步撤消没有什么大的作用。

回避方法4: 不要执行异步撤消处理

最后是、不要执行异步撤消处理。反而代之的是、

  • 设计成不依赖使用异步撤消那样的处理

或者

  • 不得不使用线程撤消的话、不做异步撤消而作延迟撤消的处理

这是比较实际的做法,是我们值得推荐的。

*1 :pthread_setcanceltype 函数

*2 :pthread_setcancelstate 函数

*3 :Solaris 和HP-UX 等

 

准则 5: 尽可能避免线程中做延迟撤销的处理

l        线程的异步撤消是指:一个线程发出中断其他线程的处理的一个动作

l        延迟撤消因为是规格自由度比较高,所以根据OS 和C 库函数的版本它也有各式各样的动作

Ø        要想在不同的环境下都能稳定的动作的话,就必须要详细调查运行环境和,对C 库函数进行抽象化,做必要的条件编译

Ø        在C++ 中,「撤消发生时的对象释放」的实现不具有可移植性

l        线程撤销要慎重使用。在C++ 里不要使用

 

说明:

在前面我们已经讲过,线程的撤消分为「异步」「延迟」这两种类型、并且「异步撤消」也是非常容易引起各种复杂问题的元凶。

那么,现在要在程序中除掉「延迟撤消」。延迟撤消虽然不会像异步撤消那样会引起各种各样的问题、但是,注意事项还是有很多的。只有把下面的这些注意事项全部都把握之后才能放心使用。

注意事项1: 要好好把握撤消点

和异步撤消不一样的是:撤消处理一直会被延迟到在代码上明示出来的撤消点之后才会被执行。 如果编写了一个具有延迟撤消可能的代码,代码中的那条语句是撤消点、必须要正确的把握。

首先,调用过pthread_testcancel 函数的地方就变成撤消点了。 当然这个函数是:仅仅为了「变成延迟撤消」的目的而设置出来的函数。除此之外,某些标准库函数被调用后会不会变成撤消点是在规格(SUSv3) 中决定的。请参照规格说明 、有下面的函数一览。

 

下面的函数 撤消点

accept, aio_suspend, clock_nanosleep, close, connect, creat, fcntl, fdatasync,fsync, getmsg, getpmsg, lockf, mq_receive, mq_send, mq_timedreceive,mq_timedsend, msgrcv, msgsnd, msync, nanosleep, open, pause, poll, pread,pselect, pthread_cond_timedwait, pthread_cond_wait, pthread_join,pthread_testcancel, putmsg, putpmsg, pwrite, read, readv, recv, recvfrom,( 略)

 

下面的函数不是 撤消点

access, asctime, asctime_r, catclose, catgets, catopen, closedir, closelog,ctermid, ctime, ctime_r, dbm_close, dbm_delete, dbm_fetch, dbm_nextkey, dbm_open,dbm_store, dlclose, dlopen, endgrent, endhostent, endnetent, endprotoent,endpwent, endservent, endutxent, fclose, fcntl, fflush, fgetc, fgetpos, fgets,fgetwc, fgetws, fmtmsg, fopen, fpathconf, fprintf, fputc, fputs, fputwc, fputws,( 略)

 

看到这些我想已经明白了,但是在规格中也说明了「能否成为撤消点跟具体的实现相关的函数」也是多数存在的。原因是:为了可移植性、保证「在一定的时间内让线程的延迟撤消完成」是很困难的事情 *1 。做的不好的话、只要稍微一提升OS 的版本就可能让做出来的程序产品不能动作。

即使是这样那还想要使用延迟撤消吗?

注意事项2: 实现要知道cleanup 函数的必要性

可能被延迟撤销的线程在运行的过程中,要申请资源的场合,一定要考虑到以下的几点,否则就会编制出含有资源丢失和死锁的软件产品。

例如编写的下面的函数就不能被安全的延迟撤销掉。

void* cancel_unsafe(void*) {
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&mutex);                     // 此处不是撤消点
    struct timespec ts = {3, 0}; nanosleep(&ts, 0); // 经常是撤消点
    pthread_mutex_unlock(&mutex);                   // 此处不是撤消点
    return 0;
}
int main(void) {
    pthread_t t;
    // pthread_create 后马发上收到一个有效的延迟撤消的要求
    pthread_create(&t, 0, cancel_unsafe, 0);
    pthread_cancel(t); 
    pthread_join(t, 0);
    cancel_unsafe(0); // 发生死锁!
    return 0;
}

在上面的样例代码中,nanosleep 执行的过程中经常会触发延迟撤销的最终动作,但是这个时候的mutex 锁还处于被锁定的状态。而且,线程一被延迟撤消的话就意味着没有人去释放掉这个互斥锁了 *2 。因此,在下面的main 函数中调用同样的cancel_unsafe 函数时就会引起死锁了。

为了回避这个问题,利用pthread_cleanup_push 函数在撤消时释放掉互斥锁的话就OK 了,也就不会死锁了。

// 新增清除函数
void cleanup(void* mutex) { 
    pthread_mutex_unlock((pthread_mutex_t*)mutex);
}

// 粗体字部分是新增的语句
void* cancel_unsafe(void*) {
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cleanup_push(cleanup, &mutex); 
    pthread_mutex_lock(&mutex);
    struct timespec ts = {3, 0}; nanosleep(&ts, 0);
    pthread_mutex_unlock(&mutex);
    pthread_cleanup_pop(0); 
    return 0;
}

 

注意事项3: 实现要清楚延迟撤消和C++ 之间的兼容度

使用C 语言的场合,利用上面的pthread_cleanup_push/pop 函数就能安全地执行延迟撤消的动作,但是在C++ 语言的场合就会出现其他的问题。C++ 与延迟撤消之间的兼容度是非常差的 。具体的表现有以下两个问题:

  1. 执行延迟撤消的时候,内存栈上的对象的析构函数会不会被调用跟具体的开发环境有关系
    • GCC3 版本就不会调用。
    • Solaris 和Tru64 UNIX 下的原生编译器的场合,就调用析构函数( 好像)
  2. pthread_cleanup_push/pop 函数和C++ 的异常处理机制之间有着怎样的相互影响也能具体环境有关

不调用析构函数,或者在抛出异常的时候不能做cleanup 处理,经常是发生内存泄漏,资源丢失,程序崩溃,死锁等现象的原因。令人意外的是对于这个深层次的问题,就连Boost C++库 都束手无策。

[Q] Why isn't thread cancellation or termination provided?

[A] There's a valid need for thread termination, so at some point Boost.Threads probably will include it, but only after we can find a truly safe (and portable) mechanism for this concept.

先必须确保对象的自由存储,而后全都让cleanup 函数去释放对象的方法也有,但是这次是牺牲了异常安全性。

应该说的是,在使用C++ 的工程里不对线程进行延迟撤消处理还是比较实际的。

*1 :好的问题是 gethostbyname() 函数

*2 :异步撤消跟malloc 函数的例子很相似

 

准则 6: 遵守多线程编程的常识

1.    要准确把握在POSIX 标准的函数中,那些函数是非线程安全的,一定不要使用

2.    要让自己编写的函数符合线程安全

o      在访问共享数据/ 变量之前一定要先锁定

o      如果使用C++ 的话,一定要注意函数的同步方法

  (1) 要准确把握那些非线程安全的函数,一定不要使用

如果在POSIX 平台上进行多线程编程时,有几个最基本的知识,也就是所说的“ 常识” ,希望大家一定要严格遵守。

首先,我们要理解“ 线程安全” 的意思。线程安全的函数就是指,“ 一个能被在多个线程同时调用也不会发生问题的函数” 。 这样的函数通常要满足以下几个的特质。

  1. 不要操作局部的静态变量( 函数内的static 变量) 和全局静态数据( 全局变量,函数外的静态变量) 。而且,也不要调用其他的非线程安全的函数
  2. 如果要操作这样的变量的话,事先必须使用互斥锁mutex 进行同步,否则一定要限制多个线程同时对它的访问

那么、在POSIX 标准的函数里面,也有不满足上述条件的。由于历史遗留问题,一些函数的识别标识(signature) 的定义没有考虑到线程安全的问题,所以不管怎么做都不能满足上述的条件。例如,看看 localtime函数 吧。它的定义的识别标识(signature )如下:

struct tm *localtime(const time_t *timer);

localtime 函数是,把一个用整数形式表示的时刻( 从1970/1/1 到现在为止的秒数) 、转换成一个能让人容易明白的年月日形式表示出来的tm 结构体并返回给调用者 的函数。根据规格说明、返回出来的tm 结构体是不需要free() 掉,也不能释放的。这个函数典型的实现就像下面的代码那样:

struct tm *localtime(const time_t *timer) {
  static struct tm t;
  
  /* ... 从timer 参数里算出年月日等数值 ... */

  t.tm_year = XXX;
  /* ... 把它们填入到结构体内... */
  t.tm_hour = XXX;
  t.tm_min  = XXX;
  t.tm_sec  = XXX;

  return &t;
}

这个函数如果被像下面那样使用的话,就会有漏洞:

1.    在线程A 里执行 ta = localtime(x);

2.    在线程B 里执行 tb = localtime(y);

3.    线程A 参照ta 结构体里的数据 → 就发现这些数据是一些奇怪的值!

在函数的说明手册里对这个问题也没有做过详细的说明。关于这个漏洞,在localtime 函数即使使用了mutex 锁也不能被回避掉。所以,这个函数定义的识别标识是不行滴。
译 者lymons 注:在多个线程里调用localtime 函数之所以有问题的原因是,localtime 函数里返回的tm 构造体是一个静态的结构体,所以在 线程A 里调用localtime 函数时,该结构体被赋予正确的值;而在线程A 参照这个结构体之前,线程B 又调用localtime 的话,这个静态的结构体 又被赋予新的一个值。因此在线程A 对这个结构体的访问都是基于一个错误的值进行的]

正因为如此,就像上面说过的POSIX 规格(SUSv3) 里整齐的定义了 一些“ 非线程安全的函数” 。在"§2.9.1 Thread-Safety" 这里登载了的非线程安全的函数有如下所示。

asctime, basename, catgets, crypt, ctime, dbm_clearerr, dbm_close, dbm_delete, dbm_error, dbm_fetch, dbm_firstkey, dbm_nextkey, dbm_open, dbm_store, dirname, dlerror, drand48, ecvt, encrypt, endgrent, endpwent, endutxent, fcvt, ftw, gcvt, getc_unlocked, getchar_unlocked, getdate, getenv, getgrent, getgrgid, getgrnam,

( 省略)

对于在规格中被定义为非线程安全的函数,应该制定一个避免使用它们的规则出来,并且制作一个能够自动检查出是否使用了这些函数的开发环境,应该是比较好的。

反之,在这里没有被登载的POSIX 标准函数都被假定为 "shall be thread-safe" 的、所以在实际的使用中可以认为在多线程环境里是没有问题的( 而且在使用的平台上没有特别地说明它是非线程安全的话) 。

另外,有几个非线程安全的函数,都准备了一个备用的线程安全版本的函数( 仅仅是变更了函数的识别标识) 。像这些函数为了与原版进行区别都在其函数名后面添加了 _r 这个后缀 *1 。例如,asctime 函数就有线程安全版本的函数asctime_r 。在规格说明中是否定义了备用函数,可以试着点击刚才的那个网页 里面的函数名就可以看到。点击 rand函数 就可以看到,

[TSF] int rand_r(unsigned *seed);

用[TSF] 这样的文字标记出来的函数吧。这就是备用函数。在一览中没有记载出来的函数( 备注: 稍微有点儿出入。请参照这里 ) 、据我所知还有下面的备用函数。

asctime_r, ctime_r, getgrgid_r, getgrnam_r, getpwnam_r, getpwuid_r, gmtime_r, localtime_r, rand_r, readdir_r, strerror_r, strtok_r

还有,在规格以外,还准备了很多的下面那样的函数。

gethostbyname_r, gethostbyname2_r

在最近的操作系统中、也使用 getaddrinfo API 函数来解决IPv6 名字对应的问题。gethostbyname 系列的API 都是比较陈旧的函数了、所以使用前面的函数还是比较好吧 *2 。根据规格SUSv3 ,getaddrinfo 也是线程安全的:

The freeaddrinfo() and getaddrinfo() functions shall be thread-safe.

在多线程编程中,不要使用非线程安全的函数,而他们的备用函数可以放心地积极的去使用。

后续

*1 :在C 言語里函数不能重载,所以只能添加一个新的函数

*2 :跟网络有关的API 哪些是新的哪些是旧的,可以参考 IPv6网络编程 (network technology series) 这本好书

 

(2 )要让自己编写的函数符合线程安全

在写多线程的应用程序时, 在多个线程里共享的变量要先锁定然后在更新它。那么在多线程里共享的变量主要有全局变量和函数内的静态变量。而且, 即使是short 型和int 型的共享变量也要先锁定后更新才能保证其安全。

※ 详细的是参考 id:yupo5656:20040618 "[C++] 多线程和共享变量"

还有, 在使用C++ 编程的场合要注意函数的方步方法。一般的说来下面的写法是错误的。Mutex 在函数内被声明成静态变量是不允许的 。

int incr_counter(void) {
  static Mutex m;  // 这么写不行
  m.Lock();

  static int counter = 0;
  int ret = ++counter;

  m.Unlock();
  return ret;
}

应该用下面的方式来代替,

Mutex m;

int incr_counter(void) {
  m.Lock();
  // ...

把Mutex 声明成全局变量的话比较好( 稍微比上一个好) 。

 

※ 详细是参考 id:yupo5656:20040713 "[C++] C++ 中写出synchronized method 比较难" 。

 

UNIX 上C++ 程序设计守则(6)-- 补记

线程安全函数是像下面那样

  1. 不要操作局部的静态变量( 函数内的static 型的变量) 和非局部的静态数据( 全局变量) 。并且, 其它的非线程安全函数不要调用
  2. 要操作这样的变量的话, 就要使用mutex 进行同步处理, 来限制多个线程同时对它进行操作

被定义的, 但是

  • 特别是前者, 和被叫做可重入的(reentrant) 函数有区别
  • 反之, 后者特别是和叫做"Serializable"( 不单单是MT-Safe)"Safe" 的函数有区别

也有以上的情况。在Solaris 的man 手册里, 用后者的方式进行区别. 从多线程程序里安全调用的话, 就叫做"Safe", 而且, 在多线程中能够并发(concurrency) 地执行这个函数的处理的话,好像 就叫做"MT-Safe" 。

嗯, 因为比较详细的, 如果不是在对于执行速度要求比较苛刻的环境中编写代码的话, 单单地意识到「是否线程安全」就足够了, 不是吗。

 

附录一:可重入与异步信号安全、线程安全

一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入 OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误。

《多线程编程指南》中定义,可以被信号控制器安全调用的函数被称为 " 异步信号安全 " 函数。

 

CSAPP

13.7.1 线程安全

一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。

13.7.2 可重入性

有一类重要的线程安全函数,叫做可重入函数,其特点在于它们具有一种属性:当它们被多个线程调用时,不会引用任何共享的数据。

 

尽管线程安全和可重入有时会(不正确的)被用做同义词,但是它们之间还是有清晰的技术差别的。可重入函数是线程安全函数的一个真子集。

重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括 static ) ,这样的函数就是 purecode (纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。简单讲:可重入函数即不用有状态的函数。

可重入函数是线程安全函数,但是反过来,线程安全函数未必是可重入函数。

 

实际上,可重入函数很少, APUE 10.6 节中描述了 Single UNIX Specification 说明的可重入的函数,只有115 个; APUE 12.5 节中描述了 POSIX.1 中不能保证线程安全的函数,只有 89 个。

信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。 如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。

不可重入函数的原因在于:

1> 已知它们使用静态数据结构

2> 它们调用 malloc 和 free.

因为 malloc 通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正在修改此链接表。

3> 它们是标准 IO 函数 .

因为标准 IO 库的很多实现都使用了全局数据结构

 

即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是 errno 。一个线程中只有一个 errno变量,信号处理函数中使用的可重入函数也有可能会修改 errno 。 例如, read 函数是可重入的,但是它也有可能会修改 errno 。因此,正确的做法是在信号处理函数开始,先保存 errno ;在信号处理函数退出的时候,再恢复 errno 。

 

例如,程序正在调用 printf 输出,但是在调用 printf 时,出现了信号,对应的信号处理函数也有 printf 语句,就会导致两个 printf 的输出混杂在一起。注意:这回导致死锁!!!

对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。

屏蔽信号的方法:

1> signal(SIGPIPE, SIG_IGN);    // 忽略一些信号

2> sigprocmask()

sigprocmask 只为单线程定义的

3> pthread_sigmask()

pthread_sigmasks 可以在多线程中使用

 

附录二: Single UNIX Specification 说明的可重入的函数

Reentrant functions that may be called from a signal handler

accept

fchmod

lseek

sendto

stat

access

fchown

lstat

setgid

symlink

aio_error

fcntl

mkdir

setpgid

sysconf

aio_return

fdatasync

mkfifo

setsid

tcdrain

aio_suspend

fork

open

setsockopt

tcflow

alarm

fpathconf

pathconf

setuid

tcflush

bind

fstat

pause

shutdown

tcgetattr

cfgetispeed

fsync

pipe

sigaction

tcgetpgrp

cfgetospeed

ftruncate

poll

sigaddset

tcsendbreak

cfsetispeed

getegid

posix_trace_event

sigdelset

tcsetattr

cfsetospeed

geteuid

pselect

sigemptyset

tcsetpgrp

chdir

getgid

raise

sigfillset

time

chmod

getgroups

read

sigismember

timer_getoverrun

chown

getpeername

readlink

signal

timer_gettime

clock_gettime

getpgrp

recv

sigpause

timer_settime

close

getpid

recvfrom

sigpending

times

connect

getppid

recvmsg

sigprocmask

umask

creat

getsockname

rename

sigqueue

uname

dup

getsockopt

rmdir

sigset

unlink

dup2

getuid

select

sigsuspend

utime

execle

kill

sem_post

sleep

wait

execve

link

send

socket

waitpid

_Exit & _exit

listen

sendmsg

socketpair

write

 

附录三: POSIX.1 中不能保证线程安全的函数

Figure 12.9. Functions not guaranteed to be thread-safe by POSIX.1

asctime

ecvt

gethostent

getutxline

putc_unlocked

basename

encrypt

getlogin

gmtime

putchar_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

nftw

unsetenv

dlerror

gethostbyaddr

getutxent

nl_langinfo

wcstombs

drand48

gethostbyname

getutxid

ptsname

wctomb

 

附录四:线程撤销点

Cancellation points defined by POSIX.1

accept

mq_timedsend

putpmsg

sigsuspend

aio_suspend

msgrcv

pwrite

sigtimedwait

clock_nanosleep

msgsnd

read

sigwait

close

msync

readv

sigwaitinfo

connect

nanosleep

recv

sleep

creat

open

recvfrom

system

fcntl2

pause

recvmsg

tcdrain

fsync

poll

select

usleep

getmsg

pread

sem_timedwait

wait

getpmsg

pthread_cond_timedwait

sem_wait

waitid

lockf

pthread_cond_wait

send

waitpid

mq_receive

pthread_join

sendmsg

write

mq_send

pthread_testcancel

sendto

writev

mq_timedreceive

putmsg

sigpause

 

 

Optional cancellation points defined by POSIX.1

catclose

ftell

getwc

printf

catgets

ftello

getwchar

putc

catopen

ftw

getwd

putc_unlocked

closedir

fwprintf

glob

putchar

closelog

fwrite

iconv_close

putchar_unlocked

ctermid

fwscanf

iconv_open

puts

dbm_close

getc

ioctl

pututxline

dbm_delete

getc_unlocked

lseek

putwc

dbm_fetch

getchar

mkstemp

putwchar

dbm_nextkey

getchar_unlocked

nftw

readdir

dbm_open

getcwd

opendir

readdir_r

dbm_store

getdate

openlog

remove

dlclose

getgrent

pclose

rename

dlopen

getgrgid

perror

rewind

endgrent

getgrgid_r

popen

rewinddir

endhostent

getgrnam

posix_fadvise

scanf

endnetent

getgrnam_r

posix_fallocate

seekdir

endprotoent

gethostbyaddr

posix_madvise

semop

endpwent

gethostbyname

posix_spawn

setgrent

endservent

gethostent

posix_spawnp

sethostent

endutxent

gethostname

posix_trace_clear

setnetent

fclose

getlogin

posix_trace_close

setprotoent

fcntl

getlogin_r

posix_trace_create

setpwent

fflush

getnetbyaddr

posix_trace_create_withlog

setservent

fgetc

getnetbyname

posix_trace_eventtypelist_getnext_id

setutxent

fgetpos

getnetent

posix_trace_eventtypelist_rewind

strerror

fgets

getprotobyname

posix_trace_flush

syslog

fgetwc

getprotobynumber

posix_trace_get_attr

tmpfile

fgetws

getprotoent

posix_trace_get_filter

tmpnam

fopen

getpwent

posix_trace_get_status

ttyname

fprintf

getpwnam

posix_trace_getnext_event

ttyname_r

fputc

getpwnam_r

posix_trace_open

ungetc

fputs

getpwuid

posix_trace_rewind

ungetwc

fputwc

getpwuid_r

posix_trace_set_filter

unlink

fputws

gets

posix_trace_shutdown

vfprintf

fread

getservbyname

posix_trace_timedgetnext_event

vfwprintf

freopen

getservbyport

posix_typed_mem_open

vprintf

fscanf

getservent

pthread_rwlock_rdlock

vwprintf

fseek

getutxent

pthread_rwlock_timedrdlock

wprintf

fseeko

getutxid

pthread_rwlock_timedwrlock

wscanf

fsetpos

getutxline

pthread_rwlock_wrlock

 



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值