1. 程式人生 > >muduo原始碼分析:Thread類

muduo原始碼分析:Thread類

程式碼檔案目錄為:muduo/base

 

ThreadNameInitializer

ThreadNameInitializer進行主執行緒初始化操作(利用全域性變數):包括設定預設的執行緒name、快取執行緒id。如果進行了fork,那麼在子程序中執行afterFork函式進行同樣的初始化工作。

void afterFork()
{
  muduo::CurrentThread::t_cachedTid = 0;
  muduo::CurrentThread::t_threadName = "main";
  CurrentThread::tid();
  // no need to call pthread_atfork(NULL, NULL, &afterFork);
}

class ThreadNameInitializer
{
 public:
  ThreadNameInitializer()
  {
    muduo::CurrentThread::t_threadName = "main";
    CurrentThread::tid();
    pthread_atfork(NULL, NULL, &afterFork);
  }
};

ThreadNameInitializer init;

 

Thread

Thread.h

class Thread : boost::noncopyable        //不允許複製
{
 public:
  typedef boost::function<void ()> ThreadFunc;

  explicit Thread(const ThreadFunc&, const string& name = string());
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  explicit Thread(ThreadFunc&&, const string& name = string());
#endif
  ~Thread();

  void start();		// 啟動執行緒
  int join();		// return pthread_join()

  bool started() const { return started_; }
  // pthread_t pthreadId() const { return pthreadId_; }
  pid_t tid() const { return tid_; }            //返回執行緒id
  const string& name() const { return name_; }    //返回執行緒名

  static int numCreated() { return numCreated_.get(); }		//返回建立的執行緒數

 private:
  void setDefaultName();

  bool       started_;                   //啟動標識
  bool       joined_;
  pthread_t  pthreadId_;		// pthread_t給pthraed_xxx函式使用
  pid_t      tid_;			// pid_t作為執行緒標識
  ThreadFunc func_;
  string     name_;
  CountDownLatch latch_;

  static AtomicInt32 numCreated_;	//記錄建立了多少執行緒
};

Thread既有pthread_t也有pid_t,它們各有用處,pthread_t給pthread_XXX函式使用,而pid_t作為執行緒標識。


為什麼使用pid_t而不使用pthread_t來標識執行緒id呢?

pthread_t的值很大,無法作為一些容器的key值。 glibc的Pthreads實現實際上把pthread_t作為一個結構體指標,指向一塊動態分配的記憶體,但是這塊記憶體是可以反覆使用的,也就是說很容易造成pthread_t的重複。也就是說pthreads只能保證同一程序內,同一時刻的各個執行緒不同;不能保證同一個程序全程時段每個執行緒具有不同的id,不能保證執行緒id的唯一性。

在LINUX系統中,建議使用gettid()系統呼叫的返回值作為執行緒id,這麼做的原因: 
返回值是一個pid_t,其值是一個很小的整數,方便輸出。 
在linux系統中,它直接標識核心任務排程id,可通過/proc檔案系統中找到對應項:/proc/tid 或者 /proc/pid/task/tid,方便定位到具體執行緒。任何時刻都是唯一的,並且由於linux分配新的pid採用遞增輪迴辦法,短時間內啟動多個執行緒也會具有不同的id。


建構函式Thread::Thread()

Thread::Thread(const ThreadFunc& func, const string& n)
  : started_(false),
    joined_(false),
    pthreadId_(0),
    tid_(0),
    func_(func),
    name_(n),
    latch_(1)
{
  setDefaultName();
}

執行緒池中呼叫Thread建構函式如下:

//將this分配給runInThread,相當於構造Thread(this.runInThread,name+id)並加入執行緒陣列。執行緒函式是runInThread 
threads_.push_back(new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));	

    threads_[i].start();	//啟動每個執行緒,但是由於執行緒執行的函式是runInThread,所以會阻塞

執行緒函式即為:this.runInThread,執行緒名為name+id。  (注意此處的runInThread不是Thread類中的,而是ThreadPool中的)

 

 

執行緒的預設name與它是第幾個執行緒相關,std::string 沒有format,那麼格式化可以使用snprintf,或者使用ostringstream,或者boost::format也是可以的。

void Thread::setDefaultName()
{
  int num = numCreated_.incrementAndGet();
  if (name_.empty())
  {
    char buf[32];
    snprintf(buf, sizeof buf, "Thread%d", num);
    name_ = buf;
    //name_ = str(boost::format("Thread%1%") % num); // 使用boost::format
  }
}

Thread::start()

執行緒啟動函式,呼叫pthread_create建立執行緒,執行緒函式為detail::startThread,傳遞給執行緒函式的引數data是在heap上分配的,data存放了執行緒真正要執行的函式記為func、執行緒id、執行緒name等資訊。detail::startThread會呼叫func啟動執行緒,所以detail::startThread可以看成是一個跳板或中介。

void Thread::start()			//執行緒啟動函式,呼叫pthread_create建立執行緒
{
  assert(!started_);			//確保執行緒沒有啟動
  started_ = true;		        //設定標記,執行緒已經啟動 
  // FIXME: move(func_)
  detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_); //data存放了執行緒真正要執行的函式,記為func,執行緒id,執行緒name等資訊 

  //建立執行緒:執行緒函式為detail::startThread
  if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
  {
    started_ = false;	//建立執行緒失敗,設定標記執行緒未啟動		
    delete data;		// or no delete?
    LOG_SYSFATAL << "Failed in pthread_create";
  }
  else
  {
    latch_.wait();
    assert(tid_ > 0);
  }
}

detail::startThread首先將引數轉型為ThreadData*,然後呼叫data->runInThread()

void* startThread(void* obj)
{
  ThreadData* data = static_cast<ThreadData*>(obj);
  data->runInThread();
  delete data;
  return NULL;
}

runInThread()最終會呼叫func()。 (真正的執行緒函式)

  void runInThread()
  {
    *tid_ = muduo::CurrentThread::tid();
    tid_ = NULL;
    latch_->countDown();
    latch_ = NULL;

    muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str();
    ::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName);		//設定名字
    try
    {
      func_();				//呼叫真正的執行緒函式
      muduo::CurrentThread::t_threadName = "finished";
    }
    catch (const Exception& ex)
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
      fprintf(stderr, "reason: %s\n", ex.what());
      fprintf(stderr, "stack trace: %s\n", ex.stackTrace());
      abort();
    }
    catch (const std::exception& ex)
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
      fprintf(stderr, "reason: %s\n", ex.what());
      abort();
    }
    catch (...)
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str());
      throw; // rethrow
    }
  }

 

ThreadData

struct ThreadData
{
  typedef muduo::Thread::ThreadFunc ThreadFunc;
  ThreadFunc func_;
  string name_;
  pid_t* tid_;
  CountDownLatch* latch_;

  ThreadData(const ThreadFunc& func,
             const string& name,
             pid_t* tid,
             CountDownLatch* latch)
    : func_(func),
      name_(name),
      tid_(tid),
      latch_(latch)
  { }
  //....

 

 

注意:

為什麼不能直接在建立執行緒的時候執行某個類的成員函式?

因為pthread_create的執行緒函式定義為void *func(void*),無法將non-staic成員函式傳遞給pthread_create

試想,如果pthread_create的執行緒函式引數定義為boost::function<void*(void*)>,那麼結合boost::bind,就可以將一個成員函式作為引數了,像這樣:

pthread_create(&tid, NULL, boost::bind(&Class::func, &obj, _1), arg);

所以boost::function和boost::bind還是挺強大的。在C++11中已經成為標準納入到std中了。