WebRTC源碼分析-線程基礎(chǔ)之線程基本功能

前言

如之前的總述文章所述碍现,rtc::Thread類(lèi)封裝了WebRTC中線程的一般功能,比如設(shè)置線程名稱米奸,啟動(dòng)線程執(zhí)行用戶代碼昼接,線程的join,sleep悴晰,run慢睡,stop等方法逐工;同時(shí)也提供了線程內(nèi)部的消息循環(huán),以及線程之間以同步漂辐、異步方式投遞消息泪喊,同步方式在目標(biāo)線程執(zhí)行方法并返回結(jié)果等線程之間交互的方式;另外髓涯,每個(gè)線程均持有SocketServer類(lèi)成員對(duì)象袒啼,該類(lèi)實(shí)現(xiàn)了IO多路復(fù)用功能。

本文將針對(duì)rtc::Thread類(lèi)所提供的基礎(chǔ)線程功能來(lái)進(jìn)行介紹纬纪,Thread類(lèi)在rtc_base目錄下的thread.h中聲明蚓再,如下(刪除了其他非線程基礎(chǔ)功能的API,其他的API將于另外的文章中分析):

class RTC_LOCKABLE Thread : public MessageQueue {
public:
  // 線程的構(gòu)造包各,析構(gòu)
  Thread();
  explicit Thread(SocketServer* ss);
  explicit Thread(std::unique_ptr<SocketServer> ss);
  Thread(SocketServer* ss, bool do_init);
  Thread(std::unique_ptr<SocketServer> ss, bool do_init);
  ~Thread() override;
  static std::unique_ptr<Thread> CreateWithSocketServer();
  static std::unique_ptr<Thread> Create();

  // 線程的名字
  const std::string& name() const { return name_; }
  bool SetName(const std::string& name, const void* obj);

  // 當(dāng)前線程
  static Thread* Current();
  bool IsCurrent() const;

  // 阻塞權(quán)限
  bool SetAllowBlockingCalls(bool allow);
  static void AssertBlockingIsAllowedOnCurrentThread();

  // 休眠
  static bool SleepMs(int millis);

  // 線程的啟動(dòng)與停止
  bool Start(Runnable* runnable = nullptr);
  virtual void Stop();
  virtual void Run();

  // 線程的Wrap
  bool IsOwned();
  bool WrapCurrent();
  void UnwrapCurrent();

 protected:
  void SafeWrapCurrent();

  // 等待線程結(jié)束
  void Join();

 private:
#if defined(WEBRTC_WIN)
  static DWORD WINAPI PreRun(LPVOID context);
#else
  static void* PreRun(void* pv);
#endif

  bool WrapCurrentWithThreadManager(ThreadManager* thread_manager,
                                    bool need_synchronize_access);
  bool IsRunning();

  std::string name_;

#if defined(WEBRTC_POSIX)
  pthread_t thread_ = 0;
#endif
#if defined(WEBRTC_WIN)
  HANDLE thread_ = nullptr;
  DWORD thread_id_ = 0;
#endif
  bool owned_ = true;

  friend class ThreadManager;

  RTC_DISALLOW_COPY_AND_ASSIGN(Thread);
};

Thread對(duì)象的創(chuàng)建

創(chuàng)建Thread對(duì)象的構(gòu)造方法有5個(gè)摘仅,如下源碼所示:

// DEPRECATED.
Thread::Thread() : Thread(SocketServer::CreateDefault()) {}

Thread::Thread(SocketServer* ss) : Thread(ss, /*do_init=*/true) {}

Thread::Thread(std::unique_ptr<SocketServer> ss)
    : Thread(std::move(ss), /*do_init=*/true) {}

Thread::Thread(SocketServer* ss, bool do_init)
    : MessageQueue(ss, /*do_init=*/false) {
  SetName("Thread", this);  // default name
  if (do_init) {
    DoInit();
  }
}

Thread::Thread(std::unique_ptr<SocketServer> ss, bool do_init)
    : MessageQueue(std::move(ss), false) {
  SetName("Thread", this);  // default name
  if (do_init) {
    DoInit();
  }
}

需要注意的是:

  1. 默認(rèn)構(gòu)造函數(shù)Thread()被標(biāo)注為DEPRECATED,原因是其對(duì)外隱藏了一個(gè)事實(shí)问畅,即Thread對(duì)象是否與一個(gè)SocketServer對(duì)象綁定娃属。實(shí)際上該默認(rèn)構(gòu)造會(huì)創(chuàng)建一個(gè)SocketServer對(duì)象綁定到Thread對(duì)象,而大多數(shù)的應(yīng)用場(chǎng)景下Thread對(duì)象不需要SocketServer护姆。因此膳犹,源碼的注釋中告知使用Create*的兩個(gè)靜態(tài)方法來(lái)創(chuàng)建Thread對(duì)象。

  2. 兩個(gè)explicit聲明的單個(gè)入?yún)⒌臉?gòu)造函數(shù)签则,分別會(huì)調(diào)用含有兩個(gè)入?yún)⒌臉?gòu)造函數(shù),唯一的區(qū)別在于入?yún)⑹荢ocketServer*指針還是智能指針std::unique_ptr<SocketServer>類(lèi)型铐料。如果是后者那么需要使用std::move來(lái)轉(zhuǎn)移SocketServer的擁有權(quán)(std::move是一個(gè)c++11的語(yǔ)法糖渐裂,實(shí)現(xiàn)了移動(dòng)語(yǔ)義,詳細(xì)的分析可以見(jiàn)博客C++11 std::move和std::forward)钠惩。但無(wú)論是哪個(gè)構(gòu)造函數(shù)柒凉,都需要做以下三件事:
    1) 構(gòu)造父對(duì)象MQ。由于Thread繼承于MessageQueue對(duì)象篓跛,因此首先構(gòu)造MQ父對(duì)象膝捞,調(diào)用MQ的構(gòu)造函數(shù),傳入SocketServer對(duì)象指針以及布爾值false愧沟。此處傳入的SocketServer不允許為空蔬咬,否則觸發(fā)斷言;此處傳入布爾值false沐寺,告知MQ的構(gòu)造函數(shù) “DoInit()方法你就不要調(diào)用了林艘,我會(huì)在外面調(diào)用的”。
    2) 調(diào)用DoInit()混坞,該方法應(yīng)該在Thread構(gòu)造中調(diào)用狐援,而非在MQ的構(gòu)造中調(diào)用钢坦,為什么要這么做?該方法源碼如下:我們會(huì)發(fā)現(xiàn)該方法將MQ的初始化標(biāo)志置為true啥酱,并且將自身納入MQ管理類(lèi)的管理列表中爹凹。如果DoInit在MQ構(gòu)造中調(diào)用,意味著MQ構(gòu)造后镶殷,Thread對(duì)象的指針已經(jīng)暴露于外(被MQ管理類(lèi)對(duì)象持有)禾酱,此時(shí)Thread對(duì)象并未完全構(gòu)建完成,其虛表vtable還未完全建立批钠。這勢(shì)必會(huì)導(dǎo)致Thread的對(duì)象還未構(gòu)造完成時(shí)宇植,就可能會(huì)被外部使用(在別的線程中通過(guò)MessageQueueManager訪問(wèn)該對(duì)象)的風(fēng)險(xiǎn)。為了規(guī)避這樣的競(jìng)太條件埋心,因此指郁,需要給MQ的構(gòu)造傳入false,并在Thread構(gòu)造中調(diào)用DoInit()拷呆。

void MessageQueue::DoInit() {
  if (fInitialized_) {
    return;
  }

  fInitialized_ = true;
  MessageQueueManager::Add(this);
}

3)調(diào)用SetName()方法為T(mén)hread對(duì)象命名闲坎。源碼如下,需要明白的一點(diǎn)是茬斧,該方法的執(zhí)行必須在線程啟動(dòng)之前腰懂,否則會(huì)觸發(fā)斷言。并且项秉,由于是在線程啟動(dòng)之前執(zhí)行绣溜,該方法僅僅是給用戶層的Thread對(duì)象成員name_賦值而已,系統(tǒng)內(nèi)核中線程相關(guān)的結(jié)構(gòu)體還未建立娄蔼,因此怖喻,也就無(wú)法將該名稱設(shè)置到內(nèi)核。只有當(dāng)線程啟動(dòng)后岁诉,才能進(jìn)一步的將name_設(shè)置到線程的內(nèi)核結(jié)構(gòu)體中去锚沸。一般默認(rèn)名稱形如"Thread 0x04EFF758"。

bool Thread::SetName(const std::string& name, const void* obj) {
  RTC_DCHECK(!IsRunning());

  name_ = name;
  if (obj) {
    // The %p specifier typically produce at most 16 hex digits, possibly with a
    // 0x prefix. But format is implementation defined, so add some margin.
    char buf[30];
    snprintf(buf, sizeof(buf), " 0x%p", obj);
    name_ += buf;
  }
  return true;
}

兩個(gè)靜態(tài)Create*方法來(lái)創(chuàng)建Thread對(duì)象涕癣,一個(gè)傳入的是NullSocketServer對(duì)象哗蜈,該對(duì)象不持有真正的Socket,不處理網(wǎng)絡(luò)IO坠韩;另外一個(gè)傳入PhysicalSocketServer對(duì)象距潘,持有平臺(tái)相關(guān)的Socket對(duì)象,能處理網(wǎng)絡(luò)IO只搁。源碼如下:

std::unique_ptr<Thread> Thread::CreateWithSocketServer() {
  return std::unique_ptr<Thread>(new Thread(SocketServer::CreateDefault()));
}

std::unique_ptr<Thread> Thread::Create() {
  return std::unique_ptr<Thread>(
      new Thread(std::unique_ptr<SocketServer>(new NullSocketServer())));
}

線程的啟動(dòng)

線程啟動(dòng)相關(guān)的API為Start()绽昼,IsRunnning(),PreRun()须蜗,結(jié)構(gòu)體ThreadInit硅确,類(lèi)Runable目溉,平臺(tái)相關(guān)的線程啟動(dòng)函數(shù)CreateThread()以及pthread_create()。

  • Start() 方法是WebRTC中線程啟動(dòng)的標(biāo)準(zhǔn)方法菱农,算法流程如下:斷言缭付,判斷線程是否已經(jīng)啟動(dòng)-->復(fù)位底層消息循環(huán)停止標(biāo)志位-->確保ThreadManager對(duì)象的創(chuàng)建-->賦值結(jié)構(gòu)體ThreadInit->平臺(tái)相關(guān)的線程啟動(dòng)。
bool Thread::Start(Runnable* runnable) {
  // 測(cè)試環(huán)境下的斷言循未,當(dāng)前線程必須處于非運(yùn)行狀態(tài)陷猫,否則觸發(fā)Fatal Error
  RTC_DCHECK(!IsRunning());

  // 如果線程處于運(yùn)行狀態(tài)則Start返回false
  if (IsRunning())
    return false;

  // 復(fù)位消息循環(huán)stop標(biāo)志位
  Restart();  // reset IsQuitting() if the thread is being restarted

  // Make sure that ThreadManager is created on the main thread before
  // we start a new thread.
  // 確保ThreadManager在主線程中構(gòu)建
  ThreadManager::Instance();
  
  // 線程對(duì)象Thread不是Wrap而來(lái)
  owned_ = true;

  // 賦值結(jié)構(gòu)體ThreadInit
  ThreadInit* init = new ThreadInit;
  init->thread = this;
  init->runnable = runnable;

  // 平臺(tái)相關(guān)代碼,Windows系統(tǒng)下啟動(dòng)線程的妖,使用CreateThread API
#if defined(WEBRTC_WIN)
  thread_ = CreateThread(nullptr, 0, PreRun, init, 0, &thread_id_);
  if (!thread_) {
    return false;
  }
  // 類(lèi)Unix系統(tǒng)下啟動(dòng)線程绣檬,使用pthread庫(kù)
#elif defined(WEBRTC_POSIX)
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  int error_code = pthread_create(&thread_, &attr, PreRun, init);
  if (0 != error_code) {
    RTC_LOG(LS_ERROR) << "Unable to create pthread, error " << error_code;
    thread_ = 0;
    return false;
  }
  RTC_DCHECK(thread_);
#endif

  return true;
}
  • RTC_DCHECK 宏在debug模式下,被定義為RTC_CHECK嫂粟,起到斷言的作用(具體會(huì)專(zhuān)門(mén)寫(xiě)一篇文章來(lái)分析WebRTC中的斷言)娇未;在非debug模式下,RTC_DCHECK被定義為RTC_EAT_STREAM_PARAMETERS星虹,不論condition(ignored)是true or false零抬,該宏定義的最后兩行斷言代碼都不會(huì)被執(zhí)行,而宏定義的第二行(true ? true : ((void)(ignored), true))是為了不讓編譯器報(bào)ignored未被使用的警告宽涌。
// The RTC_DCHECK macro is equivalent to RTC_CHECK except that it only generates
// code in debug builds. It does reference the condition parameter in all cases,
// though, so callers won't risk getting warnings about unused variables.
#if RTC_DCHECK_IS_ON
#define RTC_DCHECK(condition) RTC_CHECK(condition)
#else
#define RTC_DCHECK(condition) RTC_EAT_STREAM_PARAMETERS(condition)
#endif

#define RTC_EAT_STREAM_PARAMETERS(ignored)                        \
  (true ? true : ((void)(ignored), true))                         \
      ? static_cast<void>(0)                                      \
      : rtc::webrtc_checks_impl::FatalLogCall<false>("", 0, "") & \
            rtc::webrtc_checks_impl::LogStreamer<>()
  • IsRunning() 代碼如下平夜。在Thread類(lèi)聲明中,Windows環(huán)境下有兩個(gè)值來(lái)表征線程卸亮,句柄類(lèi)型的HWND thread_被初始化為nullptr忽妒,整數(shù)類(lèi)型的DWORD thread_id_ 被初始化為0; 類(lèi)Unix系統(tǒng)中只有pthread_t thread_一個(gè)變量來(lái)表征線程兼贸,初始化為0段直。判斷線程是否為Running狀態(tài),只要判斷thread_是否為有效值即可寝受,因?yàn)樵赟tart()方法中啟動(dòng)線程時(shí),會(huì)調(diào)用平臺(tái)相關(guān)的方法給該參數(shù)賦有效值罕偎。
bool Thread::IsRunning() {
#if defined(WEBRTC_WIN)
  return thread_ != nullptr;
#elif defined(WEBRTC_POSIX)
  return thread_ != 0;
#endif
}
  • Restart() 是父類(lèi)MQ的方法很澄, 作用是原子操作將stop_置為0,表征該MQ未停止颜及。
void MessageQueue::Restart() {
  AtomicOps::ReleaseStore(&stop_, 0);
}
  • 初始化ThreadManager 此處即調(diào)用ThreadManager::instance()方法甩苛,確保ThreadManager在主線程中初始化。另外owned_設(shè)置為true俏站,表征Thread對(duì)象是常規(guī)的Start()方法啟用的讯蒲,而非Wrap而來(lái)。
  • ThreadInit 結(jié)構(gòu)體用于平臺(tái)相關(guān)的啟動(dòng)線程函數(shù)的入?yún)⒁拊x如下:
  struct ThreadInit {
    Thread* thread;
    Runnable* runnable;
  };

其中Runnable用來(lái)承載用戶需要執(zhí)行的代碼墨林,用于繼承Runnable并實(shí)現(xiàn)Run方法即可赁酝。

class Runnable {
 public:
  virtual ~Runnable() {}
  virtual void Run(Thread* thread) = 0;
 protected:
  Runnable() {}
 private:
  RTC_DISALLOW_COPY_AND_ASSIGN(Runnable);
};
  • 啟動(dòng)線程:
    在Windows上調(diào)用CreateThread API來(lái)啟動(dòng)線程,當(dāng)該方法執(zhí)行成功時(shí)旭等,將返回線程的句柄給thread_酌呆,線程的id給thread_id_,并在該線程上執(zhí)行PreRun()方法搔耕,init為上述ThreadInit對(duì)象隙袁,其作為入?yún)鬟f給PreRun()方法。
#if defined(WEBRTC_WIN)
  thread_ = CreateThread(nullptr, 0, PreRun, init, 0, &thread_id_);
  if (!thread_) {
    return false;
  }

在類(lèi)Unix系統(tǒng)上弃榨,使用pthread庫(kù)的pthread_create() API來(lái)啟用線程菩收,方法執(zhí)行成功對(duì)pthread thread_的成員賦值,類(lèi)似的將在新線程上執(zhí)行PreRun()方法鲸睛,并將ThreadInit結(jié)構(gòu)的對(duì)象init作為其入?yún)魅搿?/p>

#elif defined(WEBRTC_POSIX)
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  int error_code = pthread_create(&thread_, &attr, PreRun, init);
  if (0 != error_code) {
    RTC_LOG(LS_ERROR) << "Unable to create pthread, error " << error_code;
    thread_ = 0;
    return false;
  }
  RTC_DCHECK(thread_);
#endif
  • PreRun() 方法是在新啟用的線程上執(zhí)行的代碼娜饵,不同平臺(tái)下該函數(shù)聲明上略有差別,實(shí)質(zhì)上入?yún)⑹窍嗤腡hreadInit對(duì)象腊凶。本函數(shù)是本文最重要的地方划咐,涉及到了ThreadManager管理Thread類(lèi)的秘密,涉及到了用戶代碼如何在新線程上被執(zhí)行钧萍,線程內(nèi)部的消息循環(huán)是如何被運(yùn)行起來(lái)的褐缠。
    函數(shù)內(nèi)部的執(zhí)行邏輯寫(xiě)在代碼注釋上,特殊需要拆解的地方后續(xù)說(shuō)明风瘦,源碼如下:
#if defined(WEBRTC_WIN)
DWORD WINAPI Thread::PreRun(LPVOID pv) {
#else
void* Thread::PreRun(void* pv) {
#endif
  // 如前文所述队魏,ThreadInit作為入?yún)鹘oPreRun方法。
  ThreadInit* init = static_cast<ThreadInit*>(pv);
  // 將新創(chuàng)建的Thread對(duì)象納入管理万搔,與當(dāng)前線程進(jìn)行綁定胡桨。
  ThreadManager::Instance()->SetCurrentThread(init->thread);
  // 為線程設(shè)置名稱,該方法會(huì)調(diào)用平臺(tái)相關(guān)的API給線程內(nèi)核結(jié)構(gòu)體賦值上該線程的名稱瞬雹。
  rtc::SetCurrentThreadName(init->thread->name_.c_str());
 // 如果是MAC系統(tǒng)昧谊,通過(guò)pool對(duì)象的創(chuàng)建和析構(gòu)來(lái)使用oc的自動(dòng)釋放池技術(shù),進(jìn)行內(nèi)存回收酗捌。
#if defined(WEBRTC_MAC)
  ScopedAutoReleasePool pool;
#endif
  // 如果用戶需要執(zhí)行自己的代碼呢诬,那么就會(huì)繼承Runnable并實(shí)現(xiàn)Run方法,此時(shí)胖缤,正是執(zhí)行
  // 用戶代碼的時(shí)刻尚镰;否則,將執(zhí)行Thread的默認(rèn)Run方法哪廓。
  if (init->runnable) {
    init->runnable->Run(init->thread);
  } else {
    init->thread->Run();
  }
  // 到此狗唉,線程主要的活兒已干完,以下做清理工作
  // 將線程對(duì)象與當(dāng)前線程解綁
  ThreadManager::Instance()->SetCurrentThread(nullptr);
  // 釋放ThreadInit對(duì)象
  delete init;
  // 返回涡真,記得pool局部對(duì)象的釋放會(huì)觸發(fā)MAC系統(tǒng)下的自動(dòng)釋放池進(jìn)行內(nèi)存回收分俯。
#ifdef WEBRTC_WIN
  return 0;
#else
  return nullptr;
#endif
}

上述代碼需要特殊關(guān)注點(diǎn)在于:
1)新線程上PreRun()方法執(zhí)行起來(lái)后肾筐,ThreadManager立馬將當(dāng)前線程與該Thread對(duì)象關(guān)聯(lián)起來(lái),納入管理之中澳迫,當(dāng)PreRun()方法要執(zhí)行完畢了局齿,又將當(dāng)前線程與Thread對(duì)象解綁,畢竟該方法退出后橄登,線程就會(huì)停止抓歼。
2)為當(dāng)前線程設(shè)置名稱:前文已經(jīng)知道在Thread對(duì)象構(gòu)造時(shí),會(huì)給Thread的命名字段name_賦值形如"Thread 0x04EFF758"的名稱拢锹,但并未調(diào)用系統(tǒng)相關(guān)的API給線程內(nèi)核對(duì)象相關(guān)的字段賦值谣妻,因?yàn)槟莻€(gè)時(shí)候線程還未啟動(dòng),線程在系統(tǒng)內(nèi)核中還沒(méi)有相應(yīng)的對(duì)象存在呢卒稳。此時(shí)蹋半,需要做這個(gè)工作。如下就是rtc::SetCurrentThreadName(init->thread->name_.c_str()) 方法的源碼:

void SetCurrentThreadName(const char* name) {
#if defined(WEBRTC_WIN)
  struct {
    DWORD dwType;
    LPCSTR szName;
    DWORD dwThreadID;
    DWORD dwFlags;
  } threadname_info = {0x1000, name, static_cast<DWORD>(-1), 0};

  __try {
    ::RaiseException(0x406D1388, 0, sizeof(threadname_info) / sizeof(DWORD),
                     reinterpret_cast<ULONG_PTR*>(&threadname_info));
  } __except (EXCEPTION_EXECUTE_HANDLER) {  // NOLINT
  }
#elif defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(name));  // NOLINT
#elif defined(WEBRTC_MAC) || defined(WEBRTC_IOS)
  pthread_setname_np(name);
#endif
}

3)作為Mac系統(tǒng)上的特例充坑,使用了objc的自動(dòng)釋放池技術(shù)來(lái)管理內(nèi)存减江,實(shí)際上就是通過(guò)局部變量ScopedAutoReleasePool pool的構(gòu)造以及PreRun函數(shù)結(jié)束時(shí)該對(duì)象的析構(gòu)來(lái)調(diào)用objc的objc_autoreleasePoolPush()和objc_autoreleasePoolPop()進(jìn)行內(nèi)存釋放。至于其原理嘛捻爷,可以看此篇博客:自動(dòng)釋放池的前世今生 ---- 深入解析 Autoreleasepool

#if defined(WEBRTC_MAC)
#include "rtc_base/system/cocoa_threading.h"

extern "C" {
void* objc_autoreleasePoolPush(void);
void objc_autoreleasePoolPop(void* pool);
}

class ScopedAutoReleasePool {
 public:
  ScopedAutoReleasePool() : pool_(objc_autoreleasePoolPush()) {}
  ~ScopedAutoReleasePool() { objc_autoreleasePoolPop(pool_); }
 private:
  void* const pool_;
};
#endif

4)如果用戶并不想執(zhí)行自己的代碼辈灼,即不給Start方法傳入Runnabel對(duì)象,那么Thread對(duì)象提供了默認(rèn)的Run()方法在新線程上執(zhí)行也榄,該方法源碼如下巡莹。本文不展開(kāi)去敘述ProcessMessages(kForever)是如何運(yùn)作的,因?yàn)檫@屬于消息循環(huán)的內(nèi)容甜紫,會(huì)在下一篇文章中分析降宅,此處只要知道,如果用戶不運(yùn)行自己的代碼干自己的活囚霸,那么默認(rèn)的方式就是啟動(dòng)了一個(gè)消息循環(huán)不停地在此執(zhí)行腰根。

void Thread::Run() {
  ProcessMessages(kForever);
}

bool Thread::ProcessMessages(int cmsLoop) {
  // Using ProcessMessages with a custom clock for testing and a time greater
  // than 0 doesn't work, since it's not guaranteed to advance the custom
  // clock's time, and may get stuck in an infinite loop.
  RTC_DCHECK(GetClockForTesting() == nullptr || cmsLoop == 0 ||
             cmsLoop == kForever);
  int64_t msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop);
  int cmsNext = cmsLoop;

  while (true) {
#if defined(WEBRTC_MAC)
    ScopedAutoReleasePool pool;
#endif
    Message msg;
    if (!Get(&msg, cmsNext))
      return !IsQuitting();
    Dispatch(&msg);

    if (cmsLoop != kForever) {
      cmsNext = static_cast<int>(TimeUntil(msEnd));
      if (cmsNext < 0)
        return true;
    }
  }
}

線程的終止

停止一個(gè)線程,可以通過(guò)調(diào)用線程的Thread.Stop()方法來(lái)實(shí)施拓型,但千萬(wàn)不能在當(dāng)前線程上調(diào)用該方法來(lái)終止自己额嘿。MQ的Quit()方法會(huì)在介紹消息循環(huán)時(shí)來(lái)詳細(xì)解釋?zhuān)颂幾饔镁褪峭V咕€程消息循環(huán)。Join()方法在后面介紹吨述,此處作用是阻塞地等待目標(biāo)線程終止岩睁,因此钞脂,Stop函數(shù)一般會(huì)阻塞當(dāng)前線程揣云。

void Thread::Stop() {
  MessageQueue::Quit();
  Join();
}

線程的sleep,join以及阻塞權(quán)限

Thread類(lèi)中bool blocking_calls_allowed_字段控制著在該線程是否可以運(yùn)行阻塞冰啃,等待操作邓夕,比如靜態(tài)方法SleepMs刘莹,線程的Join。
-SleepMs() 方法提供了線程休眠功能焚刚,方法中先對(duì)當(dāng)前線程是否允許阻塞進(jìn)行斷言点弯,然后在Windows上調(diào)用Windows API Sleep()方法對(duì)當(dāng)前線程休眠,類(lèi)Unix系統(tǒng)上使用nanosleep()系統(tǒng)調(diào)用進(jìn)行休眠矿咕。

bool Thread::SleepMs(int milliseconds) {
  // 斷言當(dāng)前線程是否允許阻塞
  AssertBlockingIsAllowedOnCurrentThread();
 //調(diào)用不同平臺(tái)下的線程休眠函數(shù)進(jìn)行休眠
#if defined(WEBRTC_WIN)
  ::Sleep(milliseconds);
  return true;
#else
  // POSIX has both a usleep() and a nanosleep(), but the former is deprecated,
  // so we use nanosleep() even though it has greater precision than necessary.
  struct timespec ts;
  ts.tv_sec = milliseconds / 1000;
  ts.tv_nsec = (milliseconds % 1000) * 1000000;
  int ret = nanosleep(&ts, nullptr);
  if (ret != 0) {
    RTC_LOG_ERR(LS_WARNING) << "nanosleep() returning early";
    return false;
  }
  return true;
#endif
}

此處我們看一下這個(gè)是否允許阻塞的函數(shù)實(shí)現(xiàn)抢肛,該方法由NDEBUG宏來(lái)控制,意味著在debug模式下才會(huì)起作用碳柱,而非debug模式下捡絮,該函數(shù)什么也不做。在debug模式莲镣,如果當(dāng)前線程關(guān)聯(lián)了Thread對(duì)象福稳,并且其Thread.blocking_calls_allowed_字段設(shè)置為false,表示不允許該線程阻塞的情況下瑞侮,就會(huì)觸發(fā)斷言的圆。即在此Thread所關(guān)聯(lián)的線程中調(diào)用SleepMs()方法會(huì)觸發(fā)斷言,從而終止程序的運(yùn)行半火。

// static
void Thread::AssertBlockingIsAllowedOnCurrentThread() {
#if !defined(NDEBUG)
  Thread* current = Thread::Current();
  RTC_DCHECK(!current || current->blocking_calls_allowed_);
#endif
}

另外越妈,Thread也提供了一個(gè)可以設(shè)置blocking_calls_allowed_字段的方法SetAllowBlockingCalls()

bool Thread::SetAllowBlockingCalls(bool allow) {
  RTC_DCHECK(IsCurrent());
  bool previous = blocking_calls_allowed_;
  blocking_calls_allowed_ = allow;
  return previous;
}
  • Join()
    該方法用于某個(gè)線程中等待另外一個(gè)Thread所表征的線程停止。算法流程如源碼注釋?zhuān)?/li>
void Thread::Join() {
  // 判斷等待的線程對(duì)象Thread所表征的線程是否已經(jīng)停止慈缔,若已停止了叮称,那么就不需要等待了,直接返回吧
  if (!IsRunning())
    return;
  // 斷言是否是在當(dāng)前線程調(diào)用自己的Join造成自己等待自己
  RTC_DCHECK(!IsCurrent());
  // 判斷當(dāng)前線程是否具有阻塞權(quán)限藐鹤,如無(wú)瓤檐,則打印警告,但是并沒(méi)有進(jìn)行斷言
  if (Current() && !Current()->blocking_calls_allowed_) {
    RTC_LOG(LS_WARNING) << "Waiting for the thread to join, "
                        << "but blocking calls have been disallowed";
  }

  // 平臺(tái)相關(guān)的實(shí)現(xiàn)

  // Windows平臺(tái)下調(diào)用WaitForSingleObject() API進(jìn)行等待
#if defined(WEBRTC_WIN)
  RTC_DCHECK(thread_ != nullptr);
  // 等待目標(biāo)線程終止 
  WaitForSingleObject(thread_, INFINITE);
  // 關(guān)閉線程句柄
  CloseHandle(thread_);
  // 成員復(fù)位
  thread_ = nullptr;
  thread_id_ = 0;

  // 類(lèi)Unix系統(tǒng)下調(diào)用pthread庫(kù)的pthread_join()方法進(jìn)行等待
#elif defined(WEBRTC_POSIX)
  // 等待目標(biāo)線程終止 
  pthread_join(thread_, nullptr);
  // 成員復(fù)位
  thread_ = 0;
#endif
}

獲取當(dāng)前線程對(duì)象 && 判斷是否是當(dāng)前線程

獲取當(dāng)前線程Thread對(duì)象的方式直接復(fù)用了ThreadManager的CurrentThread()方法娱节,若當(dāng)前線程沒(méi)有關(guān)聯(lián)相關(guān)的Thread對(duì)象挠蛉,那么返回空指針,若當(dāng)前線程是創(chuàng)建ThreadManager對(duì)象的線程肄满,也即主線程谴古,那么如果主線程沒(méi)有關(guān)聯(lián)Thread對(duì)象,且沒(méi)有定義NO_MAIN_THREAD_WRAPPING稠歉,則會(huì)給主線程Wrap一個(gè)Thread對(duì)象掰担。

Thread* Thread::Current() {
  ThreadManager* manager = ThreadManager::Instance();
  Thread* thread = manager->CurrentThread();

#ifndef NO_MAIN_THREAD_WRAPPING
  // Only autowrap the thread which instantiated the ThreadManager.
  if (!thread && manager->IsMainThread()) {
    thread = new Thread(SocketServer::CreateDefault());
    thread->WrapCurrentWithThreadManager(manager, true);
  }
#endif
  return thread;
}

判斷Thread對(duì)象是否是當(dāng)前線程關(guān)聯(lián)的Thread,也很簡(jiǎn)單怒炸,源碼如下:

bool Thread::IsCurrent() const {
  return ThreadManager::Instance()->CurrentThread() == this;
}

線程的Wrap

Thread中Wrap相關(guān)的API有4個(gè)带饱,如下源碼所示。在WebRTC源碼分析-線程基礎(chǔ)之線程管理一文中已經(jīng)解析過(guò)Wrap相關(guān)的函數(shù),此處不再展開(kāi)表述勺疼。Wrap函數(shù)主要用于如下情形:線程啟動(dòng)不是由標(biāo)準(zhǔn)的WebRTC啟動(dòng)方式實(shí)施教寂,即不是通過(guò)調(diào)用Thread.Start()方法啟動(dòng)。那么执庐,此刻線程沒(méi)有與一個(gè)Thread對(duì)象相關(guān)聯(lián)酪耕,那么Wrap就是干這個(gè)事,將一個(gè)線程與一個(gè)Thread對(duì)象關(guān)聯(lián)起來(lái)轨淌。具體而言迂烁,有如下3件事需要做:

  • 獲取當(dāng)前線程的句柄,并賦值給Thread.thread_成員递鹉;
  • 將Thread.owned_成員設(shè)置為false婚被,表示該線程對(duì)象是Wrap而來(lái);
  • 將Thread對(duì)象與當(dāng)前線程關(guān)聯(lián)起來(lái)梳虽,納入ThreadManager的管理之中址芯。
bool WrapCurrent();
void UnwrapCurrent();
void SafeWrapCurrent();
bool WrapCurrentWithThreadManager(ThreadManager* thread_manager,
                                    bool need_synchronize_access);

總結(jié)

本文闡述了rtc::Thread類(lèi)所提供的基礎(chǔ)線程功能,分別從Thread對(duì)象的創(chuàng)建窜觉,新線程的啟動(dòng)與終止谷炸,線程阻塞權(quán)限以及線程阻塞相關(guān)的函數(shù)SleepMs、Join禀挫,獲取當(dāng)前線程旬陡、如何判斷代碼是否在當(dāng)前線程中執(zhí)行,線程的Wrap等幾個(gè)方面進(jìn)行了分析语婴。以下是需要再重點(diǎn)回顧的幾個(gè)點(diǎn):

  • Thread類(lèi)中線程相關(guān)的功能是在封裝了平臺(tái)相關(guān)API的基礎(chǔ)上實(shí)現(xiàn)的描孟,在Windows系統(tǒng)上是通過(guò)Windows API提供,在類(lèi)Unix系統(tǒng)上是通過(guò)pthread庫(kù)提供砰左。比如Win上創(chuàng)建線程CreateThread()匿醒,類(lèi)Unix上相應(yīng)為pthread_create();Win上休眠Sleep()缠导,類(lèi)Unix上是nanosleep()系統(tǒng)調(diào)用廉羔,非pthread庫(kù)功能;Win上線程join使用WaitForSingleObject()僻造,類(lèi)Unix上使用pthread庫(kù)的pthread_join()憋他;
  • Thread對(duì)象的構(gòu)造時(shí)有個(gè)需要注意的點(diǎn),那就是DoInit()方法需要Thread構(gòu)造函數(shù)中被調(diào)用髓削,否則會(huì)引發(fā)競(jìng)太條件竹挡。
  • Thread.Start()方法以及Thread.PreRun()方法是本文重點(diǎn),是WebRTC中啟動(dòng)線程的標(biāo)準(zhǔn)流程立膛。
  • Thread類(lèi)提供了線程是否允許阻塞的權(quán)限設(shè)置揪罕,由成員Thread.blocking_calls_allowed_來(lái)控制,Thread中引發(fā)線程阻塞的函數(shù),如SleepMs耸序,Join,Send等都會(huì)先進(jìn)行權(quán)限檢查鲁猩,要么打印警告日志坎怪,要么觸發(fā)斷言。
  • Thread類(lèi)的Wrap函數(shù)族提供了非標(biāo)準(zhǔn)方式啟動(dòng)的線程納入ThreadManager管理的額外方式廓握。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搅窿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子隙券,更是在濱河造成了極大的恐慌男应,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娱仔,死亡現(xiàn)場(chǎng)離奇詭異沐飘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)牲迫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)耐朴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人盹憎,你說(shuō)我怎么就攤上這事筛峭。” “怎么了陪每?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵影晓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我檩禾,道長(zhǎng)挂签,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任盼产,我火速辦了婚禮竹握,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辆飘。我一直安慰自己啦辐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布蜈项。 她就那樣靜靜地躺著芹关,像睡著了一般。 火紅的嫁衣襯著肌膚如雪紧卒。 梳的紋絲不亂的頭發(fā)上侥衬,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼轴总。 笑死直颅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怀樟。 我是一名探鬼主播功偿,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼往堡!你這毒婦竟也來(lái)了械荷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虑灰,失蹤者是張志新(化名)和其女友劉穎吨瞎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體穆咐,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颤诀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了对湃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片着绊。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖熟尉,靈堂內(nèi)的尸體忽然破棺而出归露,到底是詐尸還是另有隱情,我是刑警寧澤斤儿,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布剧包,位于F島的核電站,受9級(jí)特大地震影響往果,放射性物質(zhì)發(fā)生泄漏疆液。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一陕贮、第九天 我趴在偏房一處隱蔽的房頂上張望堕油。 院中可真熱鬧,春花似錦肮之、人聲如沸掉缺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眶明。三九已至,卻和暖如春筐高,著一層夾襖步出監(jiān)牢的瞬間搜囱,已是汗流浹背丑瞧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜀肘,地道東北人绊汹。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扮宠,于是被迫代替她去往敵國(guó)和親西乖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345