C++多线程编程的几种实现方式小结
文章目錄
- 前言
- 一、互斥鎖
- 1.mutex
- 2.lock_guard
- 3.unique_lock
- 二、條件變量
- condition_variable
- 三、信號量
- semaphore
- 四、異步操作
- 1.async構造方式
- 2.future
- 3.promise
- 五、原子操作
- 備注
前言
關于C++多線程編程的幾種實現方式(互斥鎖、條件變量、信號量、異步操作、原子操作)小結
一、互斥鎖
使用鎖的方式對共享資源對象的訪問進行控制,操作包括上鎖lock()、解鎖unlock()
1.mutex
- include <mutex>
- lock和unlock必須成對出現
- 構造出來的mutex對象都是unlock狀態
- mutex不允許拷貝構造
- 當前線程lock了mutex,該線程在unlock之前一直擁有該鎖,被鎖期間阻塞。如果是自身線程調用了lock鎖住mutex,可能會產生死鎖
- try_lock(),嘗試鎖,即使被占有也不會阻塞(失敗會返回false)
- mutex為最簡單的互斥鎖,不支持遞歸上鎖,需要遞歸上鎖要使用recursive_lock類
2.lock_guard
- 構造方式:lock_guard<mutex> lck(mtx);
- 相比較mutex最大特點是,構造時自動lock,離開作用域后調用析構函數unlock,可以與智能指針類比
- 采用資源請求初始化(RAII)編程技術,針對動態分配的資源(需要申請內存,非static對象)
- 不能手動解鎖,不能拷貝
3.unique_lock
- 比lock_guard功能更加強大,但開銷也大
- 構造時選擇加鎖方式:defer_lock延遲加鎖;try_lock嘗試加鎖;adopt_lock立刻加鎖
- release()函數,返回管理的mutex對象指針,釋放所有權(不能再對該mutex進行控制),之后用戶可以對mutex進行手動unlock操作,此功能給予更大的編程自由度,從而完成相應功能
- 配合condition_variable使用,condition_variable wait()函數第一個參數為unique_lock類型
- 不允許復制,但允許"move",即對mutex的所有權進行傳遞,lck1對mymutex所有權轉移至lck2:
unique_lock<mutex>lck1(mymutex);
unique_lock<mutex> lck2(mymutex);
二、條件變量
條件不滿足,線程被阻塞。一個線程等待某一變量的成立而阻塞,另一線程使該變量變化而使上一線程成立,同時發送信號(notify)喚醒wait的線程
condition_variable
- 獲取mutex,確切的說為mutex構造的unique_lock對象
- wait_for(),wait_until(),第二個參數為chrono(時間庫)
- wait_for(),wait()條件成立 or 超過給定時間段的時間自動unlock
- wait_until(),同上,但時間段改為時刻
- notify_all(),notify_one(),全部喚醒 or 喚醒之一
三、信號量
提供原子操作,C語言提供
semaphore
- include <semaphore.h>
- 定義:sem_t mysem;
- 初始化:int sem_init(sem_t *sem,int pshared,unsigned int value); value 參數指定信號量的初始值。一般:sem_init(&mysem,0,0);需要首次執行的:sem_init(&mysem,0,1);
pshared 參數指明信號量是由進程內線程共享,還是由進程之間共享。如果 pshared 的值為 0,那么信號量將被進程內的線程共享,并且應該放置在這個進程的所有線程都可見的地址上(如全局變量,或者堆上動態分配的變量)。
- int sem_wait(sem_t *sem); //sem+1 >0時終端阻塞,繼續執行
- int sem_post(sem_t *sem); //sem-1
- int sem_trywait(sem_t *sem);
- int sem_getvalue(sem_t *sem);
四、異步操作
在不需要等待被調用方返回之前,繼續進行后續操作。c++11提供了異步接口std::async,std::async會自動創建一個線程去調用 線程函數,它返回一個std::future,這個future中存儲了線程函數返回的結果,當我們需要線程函數的結果時,直接從future中獲取
1.async構造方式
-
template <class Fn, class… Args> future<typename
result_of<Fn(Args…)>::type>
async (Fn&& fn, Args&&… args); -
template <class Fn, class… Args> future<typename
result_of<Fn(Args…)>::type>
async (launch policy, Fn&& fn, Args&&… args); -
policy:
launch::async: 異步啟動一個新線程調用fn
launch::deferred延遲:對 fn 的調用被延遲,直到返回的未來的共享狀態被訪問(等待或獲取)。 此時,調用 fn 并且該函數不再被視為延遲。 當此調用返回時,返回的未來的共享狀態已準備就緒。
launch::async|launch::deferred:該函數自動選擇策略(在某一點)。這取決于系統和庫實現,它們通常針對系統中當前的并發可用性進行優化。 -
fn 函數指針,可以為仿函數、lambda表達式,類的成員函數等
-
args 傳遞給fn的參數
2.future
- future是一個可以從某個提供者對象或函數中檢索值的對象,如果在不同的線程中,可以正確地同步這種訪問
- template future;
template <class R&> future<R&>;
template <> future<void>; - future是與共享狀態相關聯的未來對象,并且通過調用以下函數之一來構造:
async
promise::get_future
packaged_task::get_future - get()取值、valid()共享是否有效、wait()阻塞等待(由valid判斷,false阻塞,true繼續執行)、wait_for()、wait_until()同上
3.promise
- promise是一個對象,它可以存儲一個T類型的值,供將來的對象(可能在另一個線程中)檢索,提供一個同步點。
- 這個共享狀態可以通過調用成員get_future關聯到一個未來對象。調用后,兩個對象共享相同的共享狀態: promise對象是異步提供者,應該在某個時候為共享狀態設置一個值。 future對象是一個異步返回對象,它可以檢索共享狀態的值,并在必要時等待它準備就緒。
- set_value()設置value并通知future
五、原子操作
原子操作能夠保證多個線程順序訪問,不會導致數據爭用,執行時沒有任何其它線程能夠修改相同的原子對象。原子操作更加接近底層,因而效率更高。
- std::atomic<T>構造對象
- atomic_long total(0);
- atomic<bool> flag{ false }
- 底層原理:自旋鎖 + CAS(樂觀鎖)
- 樂觀鎖:更新值時進行compareAndSwap操作,即當內存當前值等于內存預期值時,將要更新的值賦給內存,不等時不操作,之后可重試
- 自旋鎖:嘗試獲取鎖的所有權時會以忙等待的形式不斷的循環檢查鎖是否可用
備注
以上為實際遇到的問題,待補充
總結
以上是生活随笔為你收集整理的C++多线程编程的几种实现方式小结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: H5页面 点击按钮播放视频,默认全屏播放
- 下一篇: 【GAMS与C++的交互】