1. 程式人生 > 其它 >linux執行緒_使用 C++11 編寫 Linux 多執行緒程式

linux執行緒_使用 C++11 編寫 Linux 多執行緒程式

技術標籤:linux執行緒mfc多執行緒程式設計例項

前言

在這個多核時代,如何充分利用每個 CPU 核心是一個繞不開的話題,從需要為成千上萬的使用者同時提供服務的服務端應用程式,到需要同時開啟十幾個頁面,每個頁面都有幾十上百個連結的 web 瀏覽器應用程式,從保持著幾 t 甚或幾 p 的資料的資料庫系統,到手機上的一個有良好使用者響應能力的 app,為了充分利用每個 CPU 核心,都會想到是否可以使用多執行緒技術。這裡所說的“充分利用”包含了兩個層面的意思,一個是使用到所有的核心,再一個是核心不空閒,不讓某個核心長時間處於空閒狀態。在 C++98 的時代,C++標準並沒有包含多執行緒的支援,人們只能直接呼叫作業系統提供的 SDK API 來編寫多執行緒程式,不同的作業系統提供的 SDK API 以及執行緒控制能力不盡相同,到了 C++11,終於在標準之中加入了正式的多執行緒的支援,從而我們可以使用標準形式的類來建立與執行執行緒,也使得我們可以使用標準形式的鎖、原子操作、執行緒本地儲存 (TLS) 等來進行復雜的各種模式的多執行緒程式設計,而且,C++11 還提供了一些高階概念,比如 promise/future,packaged_task,async 等以簡化某些模式的多執行緒程式設計。

多執行緒可以讓我們的應用程式擁有更加出色的效能,同時,如果沒有用好,多執行緒又是比較容易出錯的且難以查詢錯誤所在,甚至可以讓人們覺得自己陷進了泥潭,希望本文能夠幫助您更好地使用 C++11 來進行 Linux 下的多執行緒程式設計。

零聲學院專門整理了Linux後臺服務開發大綱,有興趣的同學可以關注私信我(關鍵詞“Linux後臺開發”)!更多免費學習資料等你來取。

認識多執行緒

首先我們應該正確地認識執行緒。維基百科對執行緒的定義是:執行緒是一個編排好的指令序列,這個指令序列(執行緒)可以和其它的指令序列(執行緒)並行執行,作業系統排程器將執行緒作為最小的 CPU 排程單元。在進行架構設計時,我們應該多從作業系統執行緒排程的角度去考慮應用程式的執行緒安排,而不僅僅是程式碼。

當只有一個 CPU 核心可供排程時,多個執行緒的執行示意如下:

圖 1、單個 CPU 核心上的多個執行緒執行示意圖

4e643ddfd92654139dac81d6f8e91186.png

我們可以看到,這時的多執行緒本質上是單個 CPU 的時間分片,一個時間片執行一個執行緒的程式碼,它可以支援併發處理,但是不能說是真正的平行計算。

當有多個 CPU 或者多個核心可供排程時,可以做到真正的平行計算,多個執行緒的執行示意如下:

圖 2、雙核 CPU 上的多個執行緒執行示意圖

e319e8746ae04e9c214bd207a84d5803.png

從上述兩圖,我們可以直接得到使用多執行緒的一些常見場景:

  • 程序中的某個執行緒執行了一個阻塞操作時,其它執行緒可以依然執行,比如,等待使用者輸入或者等待網路資料包的時候處理啟動後臺執行緒處理業務,或者在一個遊戲引擎中,一個執行緒等待使用者的互動動作輸入,另外一個執行緒在後臺合成下一幀要畫的影象或者播放背景音樂等。
  • 將某個任務分解為小的可以並行進行的子任務,讓這些子任務在不同的 CPU 或者核心上同時進行計算,然後彙總結果,比如歸併排序,或者分段查詢,這樣子來提高任務的執行速度。

需要注意一點,因為單個 CPU 核心下多個執行緒並不是真正的並行,有些問題,比如 CPU 快取不一致問題,不一定能表現出來,一旦這些程式碼被放到了多核或者多 CPU 的環境執行,就很可能會出現“在開發測試環境一切沒有問題,到了實施現場就莫名其妙”的情況,所以,在進行多執行緒開發時,開發與測試環境應該是多核或者多 CPU 的,以避免出現這類情況。

C++11 的執行緒類 std::thread

C++11 的標準類 std::thread 對執行緒進行了封裝,它的宣告放在標頭檔案 thread 中,其中聲明瞭執行緒類 thread, 執行緒識別符號 id,以及名字空間 this_thread,按照 C++11 規範,這個標頭檔案至少應該相容如下內容:

清單 1.例子 thread 標頭檔案主要內容

namespace std{ struct thread{ // native_handle_type 是連線 thread 類和作業系統 SDK API 之間的橋樑。 typedef implementation-dependent native_handle_type; native_handle_type native_handle(); // struct id{ id() noexcept; // 可以由==, < 兩個運算衍生出其它大小關係運算。 bool operator==(thread::id x, thread::id y) noexcept; bool operator basic_ostream& operator<&out, thread::id id); // 雜湊函式 template  struct hash; template <> struct hash<:id>; }; id get_id() const noexcept; // 構造與析構 thread() noexcept; template explicit thread(F&f, Args&&… args); ~thread(); thread(const thread&) = delete; thread(thread&&) noexcept; thread& operator=( const thread&) = delete; thread& operator=(thread&&) noexcept; // void swap(thread&) noexcept; bool joinable() const noexcept; void join(); void detach(); // 獲取物理執行緒數目 static unsigned hardware_concurrency() noexcept; } namespace this_thead{ thread::id get_id(); void yield(); template void sleep_until(const chrono::time_point& abs_time); template void sleep_for(const chromo::duration& rel_time); }}

和有些語言中定義的執行緒不同,C++11 所定義的執行緒是和操作系的執行緒是一一對應的,也就是說我們生成的執行緒都是直接接受作業系統的排程的,通過作業系統的相關命令(比如 ps -M 命令)是可以看到的,一個程序所能建立的執行緒數目以及一個作業系統所能建立的總的執行緒數目等都由執行時作業系統限定。

native_handle_type 是連線 thread 類和作業系統 SDK API 之間的橋樑,在 g++(libstdc++) for Linux 裡面,native_handle_type 其實就是 pthread 裡面的 pthread_t 型別,當 thread 類的功能不能滿足我們的要求的時候(比如改變某個執行緒的優先順序),可以通過 thread 類例項的 native_handle() 返回值作為引數來呼叫相關的 pthread 函式達到目的。thread::id 定義了在執行時作業系統內唯一能夠標識該執行緒的識別符號,同時其值還能指示所標識的執行緒的狀態,其預設值 (thread::id()) 表示不存在可控的正在執行的執行緒(即空執行緒,比如,呼叫 thead() 生成的沒有指定入口函式的執行緒類例項),當一個執行緒類例項的 get_id() 等於預設值的時候,即 get_id() == thread::id(),表示這個執行緒類例項處於下述狀態之一:

  • 尚未指定執行的任務
  • 執行緒執行完畢
  • 執行緒已經被轉移 (move) 到另外一個執行緒類例項
  • 執行緒已經被分離 (detached)

空執行緒 id 字串表示形式依具體實現而定,有些編譯器為 0x0,有些為一句語義解釋。

有時候我們需要線上程執行程式碼裡面對當前呼叫者執行緒進行操作,針對這種情況,C++11 裡面專門定義了一個名字空間 this_thread,其中包括 get_id() 函式可用來獲取當前呼叫者執行緒的 id,yield() 函式可以用來將呼叫者執行緒跳出執行狀態,重新交給作業系統進行排程,sleep_until 和 sleep_for 函式則可以讓呼叫者執行緒休眠若干時間。get_id() 函式實際上是通過呼叫 pthread_self() 函式獲得呼叫者執行緒的識別符號,而 yield() 函式則是通過呼叫作業系統 API sched_yield() 進行排程切換。

如何建立和結束一個執行緒

和 pthread_create 不同,使用 thread 類建立執行緒可以使用一個函式作為入口,也可以是其它的 Callable 物件,而且,可以給入口傳入任意個數任意型別的引數:

清單 2.例子 thread_run_func_var_args.cc

int funcReturnInt(const char* fmt, ...){ va_list ap; va_start(ap, fmt); vprintf( fmt, ap ); va_end(ap); return 0xabcd;}void threadRunFunction(void){ thread* t = new thread(funcReturnInt, "%d%s