muduo库-Threadlocal类

muduo库-Threadlocal类

首先来看一个概念:线程特定数据

在单线程程序中,我们经常用全局变量共享数据。多线程环境下,全部变量被所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效。POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据称之为线程特定数据(Thread-specific Data,或TSD)。对于POD类型,可以用__thread来解决。

POD类型

POD(Plain Old Data)类型是C++ 定义的一类数据结构概念,比如 int、float 等都是 POD 类型的。Plain 代表它是一个普通类型,Old 代表它是旧的,与几十年前的 C 语言兼容,那么就意味着可以使用 memcpy() 这种最原始的函数进行操作。两个系统进行交换数据,如果没有办法对数据进行语义检查和解释,那就只能以非常底层的数据形式进行交互,而拥有 POD 特征的类或者结构体通过二进制拷贝后依然能保持数据结构不变。也就是说,能用 C 的 memcpy() 等函数进行操作的类、结构体就是 POD 类型的数据。

POSIX线程库通过四个函数操作线程特定数据,分别是pthread_key_createpthread_key_deletepthread_getspecificpthread_setspecific

一旦某个线程创建了一个key,比如key[1],那么其他线程也有自己的key[1],它们通过各自的key[1]访问到的实际数据(堆上内存分配的空间)是不同的,pthread_key_delete 只是删除key,实际数据空间的释放需要在pthread_key_create中注册一个回调函数destructordelete T*

ThreadLocal类结构图如下:

类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ifndef MUDUO_BASE_THREADLOCAL_H
#define MUDUO_BASE_THREADLOCAL_H

#include <muduo/base/Mutex.h> // MCHECK

#include <boost/noncopyable.hpp>
#include <pthread.h>

namespace muduo
{

template<typename T>
class ThreadLocal : boost::noncopyable
{
public:
ThreadLocal()
{
//构造函数中创建key,数据的销毁由destructor来销毁
MCHECK(pthread_key_create(&pkey_, &ThreadLocal::destructor));
}

~ThreadLocal()
{
//析构函数中销毁key
MCHECK(pthread_key_delete(pkey_));
}

//获取线程特定数据
T& value()
{
T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_)); //通过key获取线程特定数据
if (!perThreadValue) //如果是空的,说明特定数据还没有创建,那么就空构造一个
{
T* newObj = new T();
MCHECK(pthread_setspecific(pkey_, newObj)); //设置特定数据
perThreadValue = newObj; //返回
}
return *perThreadValue; //返回对象引用,所以需要*
}

private:

static void destructor(void *x)
{
T* obj = static_cast<T*>(x);
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; //检测是否是完全类型
T_must_be_complete_type dummy; (void) dummy;
delete obj; //如果是,我们就可以删除它了
}

private:
pthread_key_t pkey_; //key的类型是pthread_key_t类型
};

}
#endif

muduo库注册的destroy()函数直接将它的入口参数x指针强制转化为T类型指针。然后直接调用delete。这是什么原因呢?

在pthread_key_create()函数中,传入了一个&ThreadLocal::destructor的成员指针,当线成局部变量销毁时,如果传入的第二个参数不为NULL,系统将调用该函数取销毁实际的数据。由于是类成员函数,隐藏了一个指针是this指针,这时候void *x,x的地址实际上就是this的地址,也就是该对象的地址。

上面我们知道,ThreadLocal类只有一个成员,就是pthread_key_t类型成员pkey_(不是指针类型),我们可以再回头看pthread_key_create()函数,我们发现它传入的第一个参数正是pkey_的地址,也就是pthread_key_t*类型。所以我们在destructor()函数中使用

1
2
T* obj = static_cast<T*>(x);
delete obj;

由于pkey_是传入pthread_key_create()的第一个参数,所以它的地址就是实际数据存放的地址。而(void*)this == (void*)&pkey_,所以直接强制转化this指针为T*类型,然后以T*类型的方式delete,就会真实释放线程局部存储的数据。

参考:


muduo库-Threadlocal类
https://gstarmin.github.io/2023/06/27/muduo库-Threadlocal类/
作者
Starmin
发布于
2023年6月27日
许可协议