天天看點

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

c++

繼續閱讀