踩坑之路-线程私有数据
背景介绍
- 进程A
- 进程B
动态链接库
c.so -----------我们模块的so
d.so
e.so
进程A会加载c.so、d.so
进程B会加载c.so、d.so、e.so
启动d.so 和e.so的构造函数中均会创建线程私有数据
进程A每次重启时,会产生core文件,产生core的位置在c.so
进程B不会出core文件。
定位过程
发现进程A出core的地方是c.so中的私有数据,发现私有数据是乱的,怀疑是初始化时有问题。
查看代码发现函数初始化私有数据时调用pthread_getspecific(0)查看是否已经有配置,如果没有则创建私有数据
进程A调用pthread_getspecific(0)不为空,进程B调用pthread_getspecific(0)为空。
这就比较奇怪了,相同的代码,为什么结果不一样呢?想了好久,只有可能进程启动过程中已有的流程导致的,就试着把线程私有数据的各个函数(pthread_key_create、pthread_key_delete、pthread_setspecific、pthread_getspecific)均断住后启动进程。
(由于不知道是私有数据创建有问题,还是创建后又删除了,就用了这个笨办法,后来想如果先看看源码就好了,能帮助清理下思路)
启动过程中发现进程A和B启动过程果然不一样
首先加载的so不一样,进程B先加载e.so,然后是d.so;进程A先加载d.so。
更特别的是进程B第一次调用pthread_key_create创建线程私有数据的key后,没有调用pthread_setspecific设置线程私有数据。
而进程A第一次调用pthread_key_create创建线程私有数据的key后,接着调用pthread_setspecific设置线程私有数据。
猜测是进程A获取私有数据是正好是d.so中设置的私有数据,不为NULL,进程B中获取的是e.so中的私有数据,而e.so的私有数据没有设置,正好获取的是NULL。
由于都是release的.so,没办法看出来各个值是多少,只能通过查看cpu寄存器中key的入参和出参变化来验证猜想。发现第一个设置私有数据的key,key的入参和出参均是0;第二个设置私有数据时,key的入参是0,出参是1。
直接看源码,果然如此。
e.so创建了私有数据Key,没设置私有数据,创建的私有数据Key正好为0,我们模块获取私有数据的Key正好是0,获取私有数据为NULL,无巧不成书,就这么巧合的导致了进程A有问题,进程B没有问题。 |
源码
创建私有数据Key
key的分配是直接从0开始,一直向后找第一个未使用的值。
int pthread_key_create(pthread_key_t * key, destr_function destr)
{
int i;
__pthread_mutex_lock(&pthread_keys_mutex);
/* 遍历寻找第一个未使用的索引 */
for (i = 0; i < PTHREAD_KEYS_MAX; i++) {
if (! pthread_keys[i].in_use) {
/* Mark key in use */
pthread_keys[i].in_use = 1;
pthread_keys[i].destr = destr;
__pthread_mutex_unlock(&pthread_keys_mutex);
*key = i;
return 0;
}
}
__pthread_mutex_unlock(&pthread_keys_mutex);
return EAGAIN;
}
设置私有数据
先根据key查找一维空间有没有分配,如果没有分配,则创建一维空间,然后将数据挂在二维空间
int pthread_setspecific(pthread_key_t key, const void * pointer)
{
pthread_descr self = thread_self();
unsigned int idx1st, idx2nd;
if (key >= PTHREAD_KEYS_MAX || !pthread_keys[key].in_use)
return EINVAL;
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
/* 查看一维是否为空 */
if (THREAD_GETMEM_NC(self, p_specific[idx1st]) == NULL) {
void *newp = calloc(PTHREAD_KEY_2NDLEVEL_SIZE, sizeof (void *));
if (newp == NULL)
return ENOMEM;
THREAD_SETMEM_NC(self, p_specific[idx1st], newp);
}
/* 在二维空间挂在私有数据 */
THREAD_GETMEM_NC(self, p_specific[idx1st])[idx2nd] = (void *) pointer;
return 0;
}
获取私有数据
void * pthread_getspecific(pthread_key_t key)
{
pthread_descr self = thread_self();
unsigned int idx1st, idx2nd;
if (key >= PTHREAD_KEYS_MAX)
return NULL;
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
/* 判断一维空间数据是否为空,以及当前key的值是否被使用 */
if (THREAD_GETMEM_NC(self, p_specific[idx1st]) == NULL || !pthread_keys[key].in_use)
return NULL;
/* 获取当前key所对应的私有数据 */
return THREAD_GETMEM_NC(self, p_specific[idx1st])[idx2nd];
}
上一篇: 关于dsn的有关问题
下一篇: 修改php.ini不生效的问题