muduo网络库学习之ThreadLocal<T> 类、ThreadLocalSingleton<T>类封装知识点

一、ThreadLocal<T>类

1、在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。

2、在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。

3、但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。

4、POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据称为(Thread-specific Data,或 TSD)。

5、线程特定数据也称为线程本地存储TLS(Thread-local storage)

6、对于POD类型的线程本地存储,可以用__thread关键字

pthread_key_create、pthread_key_delete、pthread_getspecific、pthread_setspecific,更多信息请看这里

template<typename T> class ThreadLocal : boost::noncopyable

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

ThreadLocal()   {

    pthread_key_create(&pkey_, &ThreadLocal::destructor);

  }

当某个线程运行结束,这个线程对应的实际数据T也没有存在的价值,会调用destructor,从而delete T*;

也就是说如果有两个线程,那么destructor会执行两次,因为实际数据T在每个线程中各有一份。

key 的删除在~ThreadLocal()函数内:

~ThreadLocal()  {

    pthread_key_delete(pkey_);

 }

测试代码:

SingletonThreadLocal_test.cc:

#include <muduo/base/Singleton.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/ThreadLocal.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include <stdio.h>

class Test : boost::noncopyable
{
public:
    Test()
    {
        printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
    }

    ~Test()
    {
        printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
    }

    const std::string &name() const
    {
        return name_;
    }
    void setName(const std::string &n)
    {
        name_ = n;
    }

private:
    std::string name_;
};

#define STL muduo::Singleton<muduo::ThreadLocal<Test> >::instance().value()

void print()
{
    printf("tid=%d, %p name=%s\n",
           muduo::CurrentThread::tid(),
           &STL,
           STL.name().c_str());
}

void threadFunc(const char *changeTo)
{
    print();
    STL.setName(changeTo);
    sleep(1);
    print();
}

int main()
{
    STL.setName("main one");
    muduo::Thread t1(boost::bind(threadFunc, "thread1"));
    muduo::Thread t2(boost::bind(threadFunc, "thread2"));
    t1.start();
    t2.start();
    t1.join();
    print();
    t2.join();
    pthread_exit(0);
}

muduo::Singleton<muduo::ThreadLocal<Test> >::instance() 保证不同线程调用返回的都是同一个ThreadLocal<Test> 对象,

而不同线程调用ThreadLocal<Test>.value(); 返回的是不同的Test对象,即在不同线程中各有一份实际数据。

输出如下:

// 红色为不同Test对象的地址

simba@ubuntu:~/Documents/build/debug/bin$ ./singletonthreadlocal_test  tid=2894, constructing 0x8507038 //main thread tid=2896, constructing 0xb6200468 // thread2 tid=2896, 0xb6200468 name= tid=2895, constructing 0xb6300468 // thread1 tid=2895, 0xb6300468 name= tid=2896, 0xb6200468 name=thread2 tid=2896, destructing 0xb6200468 thread2 tid=2895, 0xb6300468 name=thread1 tid=2895, destructing 0xb6300468 thread1 tid=2894, 0x8507038 name=main one tid=2894, destructing 0x8507038 main one simba@ubuntu:~/Documents/build/debug/bin$ 

二、ThreadLocalSingleton<T>类

template<typename T>

class ThreadLocalSingleton : boost::noncopyable

{

     class Deleter { };

};

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

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

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

~Deleter() {       pthread_key_delete(pkey_); }

测试代码:

ThreadLocalSingleton_test.cc:

#include <muduo/base/ThreadLocalSingleton.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include <stdio.h>

class Test : boost::noncopyable
{
public:
    Test()
    {
        printf("tid=%d, constructing %p\n", muduo::CurrentThread::tid(), this);
    }

    ~Test()
    {
        printf("tid=%d, destructing %p %s\n", muduo::CurrentThread::tid(), this, name_.c_str());
    }

    const std::string &name() const
    {
        return name_;
    }
    void setName(const std::string &n)
    {
        name_ = n;
    }

private:
    std::string name_;
};

void threadFunc(const char *changeTo)
{
    printf("tid=%d, %p name=%s\n",
           muduo::CurrentThread::tid(),
           &muduo::ThreadLocalSingleton<Test>::instance(),
           muduo::ThreadLocalSingleton<Test>::instance().name().c_str());
    muduo::ThreadLocalSingleton<Test>::instance().setName(changeTo);
    sleep(2);  //如果没有sleep,可能thread2在thread1退出后才运行,%p可能是原来的值
    printf("tid=%d, %p name=%s\n",
           muduo::CurrentThread::tid(),
           &muduo::ThreadLocalSingleton<Test>::instance(),
           muduo::ThreadLocalSingleton<Test>::instance().name().c_str());

    // no need to manually delete it
    // muduo::ThreadLocalSingleton<Test>::destroy();
}

int main()
{
    muduo::ThreadLocalSingleton<Test>::instance().setName("main one");
    muduo::Thread t1(boost::bind(threadFunc, "thread1"));
    muduo::Thread t2(boost::bind(threadFunc, "thread2"));
    t1.start();
    t2.start();
    t1.join();
    printf("tid=%d, %p name=%s\n",
           muduo::CurrentThread::tid(),
           &muduo::ThreadLocalSingleton<Test>::instance(),
           muduo::ThreadLocalSingleton<Test>::instance().name().c_str());
    t2.join();

    pthread_exit(0);
}

输出如下:

// 红色为不同Test 对象的地址

simba@ubuntu:~/Documents/build/debug/bin$ ./threadlocalsingleton_test  tid=3341, constructing 0x8a22028  // main thread tid=3343, constructing 0xb6200468  // thread 2 tid=3343, 0xb6200468 name= tid=3342, constructing 0xb6000468  // thread 1 tid=3342, 0xb6000468 name= tid=3343, 0xb6200468 name=thread2 tid=3343, destructing 0xb6200468 thread2 tid=3342, 0xb6000468 name=thread1 tid=3342, destructing 0xb6000468 thread1 tid=3341, 0x8a22028 name=main one tid=3341, destructing 0x8a22028 main one

参考:

muduo manual.pdf

《linux 多线程服务器编程:使用muduo c++网络库》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逆向技术

内核开发知识第二讲,编写Kerner 程序中注意的问题.

什么是函数多线程安全. 简单来说就是 ,一个函数在调用过程中.还没有返回的时候.再次被其他线程调用了.但是函数执行的结果是可靠的.就可以了说这个函数是安全的.

843
来自专栏Java Edge

Java并发编程实战系列5之基础构建模块

1 同步容器类 同步容器类包括Vector和HashTable,二者是早期JDK一部分,此外还包括在JDK 1.2中添加的一些功能相似的类,这些的同步封装器类是...

3305
来自专栏码洞

Channel最佳实践之基本规则【译】

channel[通道]是golang的一种重要特性,正是因为channel的存在才使得golang不同于其它语言。channel使得并发编程变得简单容易有趣。

561
来自专栏武军超python专栏

2018年8月25日多进程编程总结

今天遇到的新单词: terminal    n终端 terminate  v结束,使终结 basic        adj基本的

1085
来自专栏java一日一条

就是要你懂 Java 中 volatile 关键字实现原理

我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发...

612
来自专栏三丰SanFeng

Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏

1 条件变量 条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。 1.1 相关函数  #include <pthread.h>  pth...

23510
来自专栏Linux驱动

8.中断按键驱动程序之poll机制(详解)

本节继续在上一节中断按键程序里改进,添加poll机制. 那么我们为什么还需要poll机制呢。之前的测试程序是这样: while (1) { read(fd, &...

1736
来自专栏代码世界

23种设计模式之单例模式

单例模式    单例模式(Singleton Pattern)是一个比较简单的模式,其定义为:Ensure a class has only one insta...

32514
来自专栏余林丰

单例模式

单例模式,顾名思义,在程序运行时有且仅有一个实例存在。最常见的一个应用场景就是网站访问量的计数器,试想如果允许创建多个实例,那还怎么计数,这个时候就得创建有且仅...

1805
来自专栏Golang语言社区

Go语言中的管道(Channel)总结

管道(Channel)是Go语言中比较重要的部分,经常在Go中的并发中使用。今天尝试对Go语言的管道来做以下总结。总结的形式采用问答式的方法,让答案更有目的性。...

3306

扫码关注云+社区