muduo源码阅读(7):ThreadLocal类和ThreadLocalSingleton类

本文详细介绍了线程特定数据(Thread-specific Data, TSD)的概念,包括POSIX线程库中的线程本地存储TLS,并通过pthread_key_create等函数展示了如何创建和使用线程本地存储。此外,还探讨了ThreadLocal类的设计,用于封装线程局部数据的存取。最后,提出了ThreadLocalSingleton类,结合单例模式和线程局部存储,确保每个线程拥有独立的单例对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.ThreadLocal类

要点

(1)了解线程特定数据
在单线程程序中,我们经常要用到“全局变量”以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。POSIX线程库通过维护一定的数据结构来解决这个问题,这个数据称为Thread-specific Data或 TSD。线程特定数据也称为线程本地存储TLS(Thread-local storage)。

对于POD类型的线程本地存储,可以用__thread关键字;对于非POD类型的线程本地存储,可以用TSD来解决。

(2)线程特定数据的用法
第一步,创建一个类型为 pthread_key_t 类型的变量。

第二步,调用 pthread_key_create() 来创建该变量。该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。

当线程中需要存储特殊值的时候,可以调用 pthread_setspecific() ,该函数有两个参数,第一个为前面声明的 pthread_key_t 变量,第二个为 void* 变量,这样你可以存储任何类型的值。

如果需要取出所存储的值,调用 pthread_getspecific() ,该函数的参数为前面提到的 pthread_key_t 变量,该函数返回 void * 类型的值。

#include <pthread.h>

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

int pthread_key_delete(pthread_key_t key);

int pthread_setspecific(pthread_key_t key, const void *value);

void *pthread_getspecific(pthread_key_t key);

在这里插入图片描述


#ifndef MUDUO_BASE_THREADLOCAL_H
#define MUDUO_BASE_THREADLOCAL_H

#include <boost/noncopyable.hpp>
#include <pthread.h>
/*
    在多线程的环境下,进程内的所有线程共享进程的数据空间。因此全局变量为所有线程共享。
在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在线程内部有效。
如常见的errno,它返回标准的错误码。errno不应该是一个局部变量。几乎每个函数都应该可以访问他,
但他又不能作为是一个全局变量。否则在一个线程里输出的很可能是另一个线程的
出错信息,这个问题可以通过创建线程的私有数据(TSD  thread specific data)来解决。在线程内部,
私有数据可以被各个函数访问。但他对其他线程是屏蔽的。
线程私有数据采用了一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,
其实是在访问不同的数据。

    pthread_key_create第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,
如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量,一键多值。
一键多值靠的是一个关键数据结构数组即TSD池,创建一个TSD就相当于将结构数组中的某一项设置为“in_use”,并将其索引返回给*key,然后设置清理函数。


     pthread_key_delete(pthread_key_t key);用来删除一个键,删除后,键所占用的内存将被释放。
 注销一个TSD,这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),
而只是将TSD释放以供下一次调用pthread_key_create()使用。需要注意的是,键占用的内存被释放。
与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放,必须在释放键之前完成。

    pthread_getspecific
    pthread_setspecific
    set是把一个变量的地址告诉key,一般放在变量定义之后,get会把这个地址读出来,然后你自己转义成相应的类型再去操作,注意变量的有效期。
只不过,在不同的线程里可以操作同一个key,他们不会冲突,比如线程a,b,c set同样的key,分别get得到的地址会是之前各自传进去的值。
这样做的意义在于,可以写一份线程代码,通过key的方式多线程操作不同的数据。
*/
namespace muduo
{

    template<typename T>
    class ThreadLocal : boost::noncopyable
    {
    public:
        ThreadLocal()
        {
            pthread_key_create(&pkey_, &ThreadLocal::destructor);       //销毁数据的函数是这个destructor,不是析构函数里的pthread_key_delete
        }

        ~ThreadLocal()
        {
            pthread_key_delete(pkey_);                              //用来删除一个键,删除后,键所占用的内存将被释放
        }



        // 返回实际数据
        // 先判定指针是否为空,为空就创建该数据并设定,不为空则直接返回该数据
        T& value()
        {
            T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_));
            if (!perThreadValue) {                  //如果该键对应的值没被设置,则设置!
                T* newObj = new T();
                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_;
    };

}
#endif

2.ThreadLocalSingleton类

前面我们讲述了单例类Singleton.h和线程局部类ThreadLocal.h,它分别提供了一个全局单例和封装线程局部数据的方法。现在如果我们如果有这样一个需求:使用一个全局单例,在每个线程内提供线程局部数据。可以这么写

muduo::Singleton<ThreadLocal<T>>::instance.value()

这样的实现略有不自然,muduo库单独实现了这样的类,这就是ThreadLocalSingleton.h

每个线程都有一个单例对象T,即每个线程都有一个T*(_thread修饰,指针是POD类型数据),每个线程都有各自的特定数据(用TSD实现),t_value 即指向实际数据T的指针。

其中instance() 的实现与Singleton 类的实现不同,因为这里是每个线程各有一个单例对象T,而不是所有线程共享一个。

deleter_ 是静态数据成员,为所有线程所共享;t_value_ 虽然也是静态数据成员,但加了__thread 修饰符,故每一个线程都会有一份。Deleter类是用来实现当某个线程执行完毕,执行注册的destructor函数,进而delete t_value_ 。key 的删除在~Deleter() 中

#ifndef MUDUO_BASE_THREADLOCALSINGLETON_H
#define MUDUO_BASE_THREADLOCALSINGLETON_H

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

namespace muduo
{

    template<typename T>
    class ThreadLocalSingleton : boost::noncopyable
    {
    public:
        static T& instance()
        {
            if (!t_value_)
            {
                t_value_ = new T();
                deleter_.set(t_value_);
            }
            return *t_value_;
        }

        static T* pointer()
        {
            return t_value_; 
        }

    private:
        ThreadLocalSingleton();
        ~ThreadLocalSingleton();

        static void destructor(void* obj)
        {
            assert(obj == t_value_);
            typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
            T_must_be_complete_type dummy; (void)dummy;
            delete t_value_;
            t_value_ = 0;
        }

        class Deleter
        {
        public:
            Deleter()
            {
                pthread_key_create(&pkey_, &ThreadLocalSingleton::destructor);//注册删除value函数
            }

            ~Deleter()
            {
                pthread_key_delete(pkey_);          //删除键
            }

            void set(T* newObj)
            {
                assert(pthread_getspecific(pkey_) == NULL);
                pthread_setspecific(pkey_, newObj);
            }

            pthread_key_t pkey_;
        };

        static __thread T* t_value_;
        static Deleter deleter_;
    };

    template<typename T>
    __thread T* ThreadLocalSingleton<T>::t_value_ = 0;

    template<typename T>
    typename ThreadLocalSingleton<T>::Deleter ThreadLocalSingleton<T>::deleter_;

}
#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值