线程本地存储
thread-local
thread-local 问题。
本页说明在 bthread 下使用 pthread-local 可能导致的问题。bthread-local 的使用方法见这里。
thread-local 问题
调用阻塞的 bthread 函数后,所在的 pthread 很可能改变,这使得pthread_getspecific、gcc __thread、C++11 的 thread_local 变量、pthread_self() 等的值发生变化,如下代码的行为是不可预期的:
thread_local SomeObject obj;
...
SomeObject* p = &obj;
p->bar();
bthread_usleep(1000);
p->bar();在 bthread_usleep 之后,该 bthread 很可能已经身处另一个 pthread,此时 p 指向的是之前 pthread 的 thread_local 变量,继续访问 p 的结果无法预计。这种使用模式往往出现在用户用线程级变量传递业务变量的场景中。为了防止这种情况,务必谨记:
- 不要用线程级变量传递业务数据。这是一种糟糕的设计模式,依赖线程级数据的函数也难以进行单元测试。判断是否滥用的方法是:如果不用线程级变量,业务逻辑是否还能正常运行?线程级变量只应作为优化手段,使用过程中不应直接或间接调用任何可能阻塞的 bthread 函数。比如使用线程级变量的 tcmalloc 就不会与 bthread 产生任何冲突。
- 如果确实(在业务中)需要使用线程级变量,请使用 bthread_key_create 和 bthread_getspecific。
gcc4 下的 errno 问题
gcc4 会优化标记为 __attribute__((const))]的函数,这个标记大致表示只要参数不变,输出就不会变。因此,当同一个函数以相同参数出现多次时,gcc4 会将其合并为一次调用。比如在我们的系统中,errno 是内容为 *__errno_location() 的宏,该函数的签名为:
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));由于此函数被标记为__const__,且没有参数,当你在一个函数中多次调用 errno 时,可能只有第一次才调用 __errno_location(),而之后只是访问其返回的int*。在 pthread 中这没有问题,因为返回的int*是 thread-local 的,在一个给定的 pthread 中是不会变化的。但是在 bthread 中,这是不成立的,因为一个 bthread 很可能在调用一些函数之后跑到另一个 pthread 去,如果 gcc 4 做了类似的优化,即把一个函数内所有的 errno 都替换为第一次调用返回的 int*,而这中间 bthread 又切换了 pthread,那么就可能会访问之前 pthread 的 errno,从而造成未定义行为。
比如下文是一种 errno 的使用场景:
Use errno ... (original pthread)
bthread functions that may switch to another pthread.
Use errno ... (another pthread)我们期望看到的行为:
Use *__errno_location() ... - the thread-local errno of original pthread
bthread may switch another pthread ...
Use *__errno_location() ... - the thread-local errno of another pthread使用 gcc4 时的实际行为:
int* p= __errno_location();
Use *p ... - the thread-local errno of original pthread
bthread context switches ...
Use *p ... - still the errno of original pthread, undefined behavior!!严格地说这个问题不是gcc4导致的,而是glibc给__errno_location的签名不够准确,一个返回thread-local指针的函数依赖于段寄存器(TLS的一般实现方式),这怎么能算const呢?由于我们还未找到覆盖__errno_location的方法,所以这个问题目前实际的解决方法是:
务必在直接或间接使用bthread的项目的gcc编译选项中添加-D__const__=,即把__const__定义为空,避免gcc4做相关优化。
把__const__定义为空对程序其他部分的影响几乎为0。另外如果你没有直接使用errno(即你的项目中没有出现errno),或使用的是gcc 3.4,即使没有定义-D__const__=,程序的正确性也不会受影响,但为了防止未来可能的问题,我们强烈建议加上。
需要说明的是,和errno类似,pthread_self也有类似的问题,不过一般pthread_self除了打日志没有其他用途,影响面较小,在-D__const__=后pthread_self也会正常。
最后修改于 2022 年 5 月 17 日:更新 brpc 用户页面 (devlive-community/knowforge#71) (a31ce10d3)
评论
登录后参与评论
KnowForge