1. 程式人生 > >muduo_base程式碼剖析之執行緒特定資料的封裝ThreadLocal

muduo_base程式碼剖析之執行緒特定資料的封裝ThreadLocal

執行緒特定資料 (執行緒內私有的全域性變數)來源

  1. 在單執行緒程式中,我們經常要用到“全域性變數”以實現多個函式間能共享資料
  2. 在多執行緒環境下,由於資料空間是共享的,因此全域性變數也為所有執行緒共享
  3. 但有時應用程式設計中有必要提供執行緒私有的全域性變數,僅在某個執行緒中生效,但卻可以跨多個函式訪問
  4. POSIX執行緒庫通過維護一定的資料結構來解決這個問題,這些資料稱為Thread-specific Data / TSD
  5. 執行緒特定資料也稱為本地儲存TLS
  6. 對於POD型別的執行緒本地儲存,可以使用__thread關鍵字

POSIX執行緒特定資料

在這裡插入圖片描述

  1. 一旦有任意一個執行緒呼叫了pthread_key_create建立了key,那麼所有的執行緒都擁有自己的key
  2. 可以為特定執行緒,設定特定資料,這樣不同的執行緒就會有不同的特定資料(雖然它們的key相同,但是key對應的實際資料不同)
  3. 怎麼銷燬執行緒的特定資料呢?答:在pthread_key_create函式中指定key的銷燬的回撥函式

muduo實現的執行緒特定資料ThreadLocal<T>

#ifndef MUDUO_BASE_THREADLOCAL_H
#define MUDUO_BASE_THREADLOCAL_H

#include
<muduo/base/Mutex.h>
// MCHECK #include <muduo/base/noncopyable.h> #include <pthread.h> namespace muduo { template<typename T> class ThreadLocal : noncopyable { private: pthread_key_t pkey_; public: ThreadLocal() { //建立執行緒特定資料pkey_,並指定銷燬實際資料的回撥函式destructor MCHECK(pthread_key_create
(&pkey_, &ThreadLocal::destructor)); } ~ThreadLocal() { MCHECK(pthread_key_delete(pkey_)); } T& value() { //pthread_getspecific:通過pkey_獲取執行緒特定資料對應的實際資料 T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_)); if (!perThreadValue) //返回值為空,說明pkey_對應的實際資料還沒建立 { T* newObj = new T(); //就建立pkey_對應的實際資料 MCHECK(pthread_setspecific(pkey_, newObj)); //為pkey_設定對應的實際資料 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; } };

測試程式與使用特定資料的方法

使用特定資料的方法

  1. 將自定義類Test,作為模板引數T傳給ThreadLocal,即muduo::ThreadLocal<Test>
  2. 定義全域性的特定資料,即muduo::ThreadLocal<Test> testObj;
    此時,所有的執行緒都擁有變數testObj,只是每個執行緒中testObj對應的實際資料是不同的
  3. 在任意一個執行緒中,可以使用testObj.value()函式獲得特定型別對應的實際的Test*,進而使用Test*去訪問Test中的資料

示例程式碼1

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

#include <stdio.h>

class Test : muduo::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 muduo::string& name() const { return name_; }
  void setName(const muduo::string& n) { name_ = n; }

 private:
  muduo::string name_;
};

//定義執行緒特定函式,表示每個執行緒都有自己獨立的特定資料
muduo::ThreadLocal<Test> testObj1;
muduo::ThreadLocal<Test> testObj2;

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

void threadFunc()
{
  print();
  testObj1.value().setName("changed 1");
  testObj2.value().setName("changed 42");
  print();
}

int main()
{
  //主執行緒
  testObj1.value().setName("main one");
  print();
  
  //執行緒1
  muduo::Thread t1(threadFunc);
  t1.start();
  t1.join();
  
  //主執行緒
  testObj2.value().setName("main two");
  print();

  pthread_exit(0);
}

示例程式碼2

/*
該示例表達的用意是:

  1. 所有的執行緒訪問的都是muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件,是相同的
  2. 但是每個執行緒中muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件對應的特定資料value()是執行緒私有的,是不同的
    */

下面程式碼總結為一句話:所有的執行緒公用一個例項instance(),但是各個執行緒擁有自己私有的特定資料instance().value()

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

#include <stdio.h>
#include <unistd.h>

class Test : muduo::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 muduo::string& name() const { return name_; }
  void setName(const muduo::string& n) { name_ = n; }

 private:
  muduo::string name_;
};
/*
該示例表達的用意是:
  1. 所有的執行緒訪問的都是muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件 
  2. 但是每個執行緒中muduo::Singleton<muduo::ThreadLocal<Test> >::instance()物件對應的
     特定資料value()是執行緒私有的,是不同的
*/
#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(std::bind(threadFunc, "thread1"));
  muduo::Thread t2(std::bind(threadFunc, "thread2"));
  t1.start();
  t2.start();
  t1.join();
  print();
  t2.join();
  pthread_exit(0);
}