Reentrancy、Thread-safe、Async-signal-safe
以前写C代码,多核多线程下时,要注意函数的可重入性,保证线程安全,即多个线程同时调用到此函数,其结果是可预期的(固定输入,固定输出),全局变量的使用需要注意加锁,线程函数中写操作必须要加锁,读操作则看实际情况来定,如果能确认业务整体逻辑上能保证写和读是有严格时序的,不需要对读进行加锁(比如写只在模块最开始初始化时完成,其他地方只是使用,不会修改,则可以读取时不需要锁)。
源于《Unix系统高级编程》一书中12.6章节示例结尾点评部分提到示例函数虽然是线程安全的,但因为调用了malloc函数,不是异步信号安全(Async-signal-safe)的,不能在信号处理句柄中使用,对此很是不解,一番搜索查询,才有所明白。
示例函数如下
char * getenv(const char *name)
{
int i, len;
char *envbuf;
pthread_once(&init_done, thread_init);
pthread_mutex_lock(&env_mutex);
envbuf = (char *)pthread_getspecific(key);
if (envbuf == NULL) {
envbuf = malloc(MAXSTRINGSZ);
if (envbuf == NULL) {
pthread_mutex_unlock(&env_mutex);
return(NULL);
}
pthread_setspecific(key, envbuf);
}
len = strlen(name);
for (i = 0; environ[i] != NULL; i++) {
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == ’=’)) {
strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);
pthread_mutex_unlock(&env_mutex);
return(envbuf);
}
}
pthread_mutex_unlock(&env_mutex);
return(NULL);
}
Note that although this version of getenv is thread-safe, it is not async-signal safe.
Even if we made the mutex recursive, we could not make it reentrant with respect to
signal handlers because it calls malloc, which itself is not async-signal safe.
-
可重入,简而言之,就是多核CPU系统能够同时多次调用该函数进行处理,且每次调用函数处理是可预期的(固定输入,固定输出)。
-
线程安全,简而言之,多个线程同时调用该函数,其处理结果总是可以预期的(固定输入,固定输出)
-
异步信号安全与系统处理信号signal句柄机制有关系。当信号发生时,系统会打断正在进行的指令执行,跳转至信号句柄指令开始执行,由于信号发生是异步的,程序执行过程中可能被任何时候的产生信号所打断,如果信号处理函数中调用了锁操作或者全局变量写操作,很有可能产生死锁或者未知行为(因为可能出现锁还未释放的情况下,信号发生;其他线程正在在修改完全局变量后,即使有锁保护,由于信号发生不可预期,句柄函数的修改会导致对应全局变量的结果无法预期)
-
malloc函数本身是线程安全的,系统调用时存在全局的heap lock(堆内存锁,保证堆内存分配时的一致连续性),信号句柄中使用malloc函数时,有可能会发生在主程序调用malloc时,但还未结束,heap lock还未释放,被信号所中断,再次调用malloc函数,就会出现同一线程对heap lock连续两次锁,即会出现死锁 deadlock
网友评论