brpc/docs/cn/thread_local.md
2022-12-14 20:13:26 +08:00

4.3 KiB
Raw Permalink Blame History

本页说明bthread下使用pthread-local可能会导致的问题。bthread-local的使用方法见这里

thread-local问题

调用阻塞的bthread函数后所在的pthread很可能改变这使pthread_getspecificgcc __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去如果gcc4做了类似的优化即一个函数内所有的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__=__unused__,即把__const__定义为一个无副作用的属性避免gcc4做相关优化。

__const__定义为__unused__对程序其他部分的影响几乎为0。另外如果你没有直接使用errno即你的项目中没有出现errno或使用的是gcc 3.4,即使没有定义-D__const__=__unused__,程序的正确性也不会受影响,但为了防止未来可能的问题,我们强烈建议加上。

需要说明的是和errno类似pthread_self也有类似的问题不过一般pthread_self除了打日志没有其他用途影响面较小-D__const__=__unused__后pthread_self也会正常。