C++小品:井水不犯河水的thread_specific_ptr,C++11线程库中的本地存储

 thread_specific_ptr 定义了一个跟线程相关的存储接口。实际上,它就是对TLS

 Thread-Locally Storage的包装。它可用于封装线程独立的全局变量。 其作用和使用方法有点类似于shared_ptr。

在每个线程中,都各自new一个对象交给全局的threah_specific_ptr进行管理,当线程退出后,他会自动释放这个对象,这一点与shared_ptr非常相似。thread_specific_ptr代表了一个全局的变量,而在每个线程中都各自new一个线程本地的对象交给它进行管理,这样,各个线程就可以各自独立地访问这个全局变量的本地存储版本,线程之间就不会因为访问同一全局对象而引起资源竞争导致性能下降。而线程结束时,这个资源会被自动释放。

它可以应用在以下两种场景:

改编一个原本设计用于单线程的库接口,比如libc里的strtok函数。这种库一般隐含的使用一个全局变量,可以使用thread_specific_ptr控制全局变量,使其可用于多线程。
线程中使用了一系列的方法/函数,它们需要一个逻辑上的全局变量来共享数据,但
实际上这个变量是线程独立的。

thread_specific_ptr代表了某个全局变量的本地存储,各个线程可以各自独立地通过它访问这个全局变量的本地副本,起到了井水不犯河水的效果。

我们来看一个实际的例子,假如我们要为一个多线程的程序添加一个记录日志的功能,记录程序的运行情况。通常的做法是,建立一个全局的日志对象,然后各个线程相互竞争地访问这个全局对象,将日志消息添加到全局的日志对象中,在这个过程中,涉及共享资源的竞争,必然会影响效率。采用线程本地存储,我们为每个线程创建一个各自独立的日志对象,并交由一个全局的thread_specific_ptr进行管理,在各个线程中,可以通过这个thread_specific_ptr独立地访问各自线程的日志对象,最后在线程退出的时候,我们再将各个线程的独自记录的日志合并到一起,就成了完整的日志。在这个过程中,各个线程通过thread_specific_ptr管理的多个日志对象,各自独立,井水不犯河水,整个过程没有共享资源的竞争,自然可以提高效率。

看代码:

// 引入需要的头文件 
#include <iostream>
#include <time.h>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/thread.hpp>
#include <boost/thread/tss.hpp>   // thread_specific_ptr

using namespace boost;
using namespace std; 
// 日志消息
class logmsg
{
public:
    logmsg(time_t t,string tid,string tmsg)
    :timeinfo(t),id(tid),msg(tmsg)
    {};
    // 通过时间比较两条日志消息
    bool operator < (const logmsg& other)
    {
        return timeinfo < other.timeinfo;
    }
    string tostring()
    {
        struct tm* ltime = localtime ( &timeinfo );
        string text(asctime(ltime));
        text += " " + id + ":" + msg;
    
        return text;
    }
private:
    time_t timeinfo;  // 时间
    string id;  // 线程id
    string msg; // 内容
};
// 日志文件
class logfile
{
public:
    logfile()
    {
    }
    logfile(int tid)
    {
        ostringstream o;
        o<<tid;
        id = o.str();
        cout<<id<<endl;
    }
    ~logfile()
    {
        cout<<"destructor"<<endl;
    }
public:
    // 记录一条日志消息到文件
    void log(string text)
    {     
        logs.push_back(logmsg(gettime(),id,text));
    }
    // 将另外一个日志文件的消息合并到本文件
    void combin(logfile* other)
    {
        logs.insert(logs.end(),
            other->logs.begin(),
            other->logs.end());
        //
    }
    // 显示所有日志消息
    void display()
    {
       // 对所有消息按照时间排序
       std::sort(logs.begin(),logs.end());
        for(auto it = logs.begin();it!=logs.end();++it)
        {
            cout<<(*it).tostring()<<endl;
        }
    }
private:
    // 获得当前时间
    time_t gettime()
    {
        return time(NULL);
    }
private:
    // 线程ID
    string id;
    // 所有日志消息
    vector<logmsg> logs;
};
mutex m;
boost::shared_ptr<logfile> cmblog(new logfile);

// 这是thread_specific_ptr的清理函数,负责清理它所管理的资源
// 他会在线程退出时被自动调用,所以我们可以在这里将这个线程记录的日志
// 合并到全局的最终日志文件中,并delete掉线程管理的日志文件
void cmb(logfile* log)
{
    mutex::scoped_lock lk(m);
    // 合并日志
     cmblog->combin(log);
    // 释放掉thread_specific_ptr管理的资源
    delete log;
    
}
// 全局的thread_specific_ptr对象,用他来管理需要共享访问的全局对象
// cmb是它的释放资源的函数
thread_specific_ptr<logfile> plog(cmb);// 

 
// 线程函数
void run(int id)
{
    // 为每个线程new一个对象,交给thread_specific_ptr进行管理
    plog.reset(new logfile(id));
    // 记录一条日志到这个线程本地存储的logfile
    plog->log("线程启动");
    
    random::uniform_int_distribution<int>  wt(100,5000);
    random::mt19937 gen;

    posix_time::milliseconds worktime(wt(gen)); 
    this_thread::sleep(worktime);    

    plog->log("线程结束");
    // 在线程结束的时候,默认情况下,它会自动delete它所管理的资源,如果我们在构造函数中提高了资源清理函数,例如这里的cmb函数,则会自动调用这个函数来完成清理工作。

// 另外,我们也可以调用它的release()函数返回它所管理的资源,自己进行处理,它不再负责资源的清理
}
int main()

    // 主线程也需要本地存储一个对象
    plog.reset(new logfile(100));
    // 记录日志
    plog->log("程序开始");
 
    random::uniform_int_distribution<int>  waittime(100,500);
    random::mt19937 gen;
    
    // 创建一个线程组
    thread_group tg;
    for(int i = 0;i<5;++i)
    {
        tg.create_thread(boost::bind(run,i));

        posix_time::milliseconds wt(waittime(gen)); 
        this_thread::sleep(wt);
    }
    // 等待所有线程结束
    tg.join_all();
    plog->log("程序结束");
     
   

    // 显示最终合并得到的日志文件
    cmblog->display();
     
    return 0;
}
 

从这个例子,我们可以体会到,线程本地存储,可以用在一些需要全局的共享访问,但是最终结果可以合并的场景。有点类似PPL中的可合并容器 concurent_vector和combinable对象。

原文链接:

https://blog.csdn.net/flyingleo1981/article/details/47083737

猜你喜欢

转载自blog.csdn.net/pzhw520hchy/article/details/83986802
今日推荐