C++11有關執行緒同步的使用
互斥量和條件變數是控制執行緒同步的常用手段,用來保護多執行緒同時訪問的共享資料。
c++11提供了這些操作,同時還提供了原子變數和一次呼叫的操作,用起來非常的方便。
我們在這裡只介紹如何在C++中使用這些同步機制,有關概念的介紹我們就不在這裡多說了。
互斥量
C++11中提供瞭如下4種語義的互斥量(mutex):
- std::mutex:獨佔的互斥量,不能遞迴使用。
- std::timed_mutex:帶超時的獨佔互斥量,不能遞迴使用。
- std::recursive_mutex:遞迴互斥量,不帶超時功能。
- std::recursive_timed_mutex:帶超時的遞迴互斥量。
獨佔互斥量std::mutex
這些互斥量的基本介面很相似,一般用法是通過lock()方法來阻塞執行緒,直到獲得互斥量的所有權為止。線上程獲得互斥量並完成任務之後,就必須使用unlock()來解除對互斥量的佔用,lock()和unlock()必須成對出現。try_lock()嘗試鎖定互斥量,如果成功則返回true,如果失敗則返回false,它是非阻塞的。
std::mutex的基本用法程式碼如下:
#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
using namespace std;
std::mutex g_lock;
void func()
{
g_lock.lock();
std::cout<<"entered thread "<<std::this_thread::get_id()<<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"leaving thread "<<std::this_thread::get_id()<<std ::endl;
g_lock.unlock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
return 0;
}
使用lock_guard可以簡化lock/unlock的寫法,同時也更安全,因為lock_guard在構造時會自動鎖定互斥量,而在退出作用域後進行析構時就會自動解鎖,從而保證了互斥量的正確操作,避免忘記unlock操作,因此,應儘量用lokc_guard。lock_guard用到了RAII技術,這種技術在類的建構函式中分配資源,在解構函式中釋放資源,保證資源在出了作用域之後就釋放。上面的例子使用lock_guard後會更簡潔,程式碼如下:
void func()
{
lock_guard<std::mutex> locker(g_lock); //出作用域之後自動解鎖
cout<<"entered thread "<<this_thread::get_id()<<endl;
this_thread::sleep_for(std::chrono::seconds(1));
cout<<"leaving thread "<<std::this_thread::get_id()<<endl;
}
遞迴的獨佔互斥量std::recursive_mutex
遞迴鎖允許同一執行緒多次獲得該互斥鎖,可以用來解決同一執行緒需要多次獲取互斥量時死鎖的問題。一個執行緒多次獲取同一個互斥量時會發生死鎖。要解決這個死鎖的問題,一個簡單的辦法就是用遞迴鎖:std::recursive_mutex,它允許同一執行緒多次獲得互斥量。
程式碼示例:
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex():i(0){}
void mul(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i*=x;
}
void div(int x)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
i/=x;
}
void both(int x,int y)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32,23); //因為同一執行緒可以多次獲取同一互斥量,不會發生死鎖
return 0;
}
需要注意的是儘量不要使用遞迴鎖好,主要原因如下:
- 需要用到遞迴鎖定的多執行緒互斥處理往往本身就是可以簡化的,允許遞迴互斥很容易放縱複雜邏輯產生,從而導致一些多執行緒同步引起的晦澀問題。
- 遞迴鎖比起非遞迴鎖,效率會低一些。
- 遞迴鎖雖然允許同一個執行緒多次獲得同一互斥量,可重複獲得的最大次數並未具體說明,一旦超過一定次數,再對lock進行呼叫就會丟擲std::system錯誤。
帶超時的互斥量
std::timed_mutex是超時的獨佔鎖,std::recursive_timed_mutex是超時的遞迴鎖,主要用在獲取鎖時超時等待功能,因為有時不知道獲取鎖需要多久,為了不至於一直在等待獲取互斥量,就設定一個等待超時時間,在超時後還可以做其他事情。
std::timed_mutex比std::mutex多了兩個超時獲取鎖的介面:try_lock_for和try_lock_until,這兩個介面是用來設定獲取互斥量的超時時間。
std::timed_mutex的基本用法如下程式碼:
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
std::timed_mutex mutex1;
void work()
{
chrono::milliseconds timeout(100);
while(true)
{
if(mutex1.try_lock_for(timeout))
{
cout<<this_thread::get_id()<<":do work with the mutex"<<endl;
chrono::milliseconds sleepDuration(250);
this_thread::sleep_for(sleepDuration);
mutex1.unlock();
this_thread::sleep_for(sleepDuration);
}
else
{
cout<<this_thread::get_id()<<": do work without mutex"<<endl;
chrono::milliseconds sleepDuration(100);
this_thread::sleep_for(sleepDuration);
}
}
}
int main()
{
thread t1(work);
thread t2(work);
t1.join();
t2.join();
return 0;
}
在上面的例子中,通過一個while迴圈不斷地去獲取超時鎖,如果超時還沒有獲取到鎖時就休眠100毫秒,再繼續獲取超時鎖。
相比std::timed_mutex,std::recursive_timed_mutex多了遞迴鎖的功能,允許同一執行緒多次獲得互斥量。
條件變數
條件變數是C++11提供的另外一種用於等待的同步機制,它能阻塞一個或多個執行緒,直到收到另外一個執行緒發出的通知或者超時,才會喚醒當前阻塞的執行緒。條件變數需要和互斥量配合起來用。C++11提供了兩種條件變數:
- condition_variable,配合std::unique_lock<std::mutex>進行wait操作。
- condition_variable_any,和任意帶有lock、unlock語義的mutex搭配使用,比較靈活,但效率比condition_variable差一些。
可以看到condition_variable_any比condition_variable更靈活,因為它更通用,對所有的鎖都適用,而condition_variable效能更好。
條件變數的使用過程如下:
- 擁有條件變數的執行緒獲取互斥量。
- 迴圈檢查某個條件,如果條件不滿足,則阻塞直到條件滿足;如果條件滿足,則向下執行。
- 某個執行緒滿足條件執行完之後呼叫notify_one或notify_all喚醒一個或者所有的等待執行緒。
我們可以看一個經典的生產者-消費者的例子:
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<unistd.h>
using namespace std;
mutex m_mutex; //互斥量
int s=0; //共享資源
condition_variable m_notempty; //條件變數
void producer() //生產者
{
while(1)
{
sleep(1);
m_mutex.lock(); //上鎖
s++;
cout<<"increase one ,s="<<s<<endl;
m_mutex.unlock();
m_notempty.notify_one();
}
}
void consumer() //消費者
{
while(1)
{
sleep(1);
unique_lock<mutex> locker(m_mutex);
while(s==0)
m_notempty.wait(locker); //使用條件變數
s--;
cout<<"decrease one,s="<<s<<endl;
}
}
int main()
{
//建立執行緒
thread thread1(producer);
thread thread2(consumer);
thread1.join();
thread2.join();
}
上面的例子中,while(s==0) m_notempty.wait(locker);
,這句程式碼的意思的是當s等於0的時候,阻塞直到條件滿足時被喚醒。
我們也可以這麼用, m_notempty.wait(locker,[]{return s>0;});
,將判斷條件放到函式裡面,意思是wait將一直阻塞,知道判斷條件滿足時,被喚醒。
原子變數
C++11提供了一個原子型別std::atomic<T>,可以使用任意型別作為模板引數,C++11內建了整型的原子變數,可以更方便地使用原子變數,使用原子變數就不需要使用互斥量來保護該變量了,因為對該變數的操作保證其是原子的,是不可中斷的。用起來更簡潔。
要做一個計時器,使用mutex時,程式碼如下:
#include<iostream>
#include<mutex>
using namespace std;
struct Counter
{
public:
int value;
std::mutex m_mutex;
void increment()
{
std::lock_guard<std::mutex> lock(mutex);
value++;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex);
value--;
}
int get()
{
return value;
}
};
如果使用原子變數,就不需要再定義互斥量了,使用更簡便。
#include<iostream>
#include<mutex>
using namespace std;
struct Counter
{
public:
std::atomic<int> value;
void increment()
{
value++;
}
void decrement()
{
value--;
}
int get()
{
return value;
}
};
call_one/once_flag的使用
為了保證在多執行緒環境中某個函式僅被呼叫一次,比如,需要初始化某個物件,而這個物件只能初始化一次時,就可以用std::call_once來保證函式在多執行緒環境中只被呼叫一次。使用std::call_once時,需要一個once_flag作為call_one的入參,它的用法比較簡單。
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
std::once_flag flag;
void do_once()
{
std::call_once(flag,[]{std::cout<<"Called once"<<endl;});
}
int main()
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
t1.join();
t2.join();
t3.join();
}
執行結果:
Called once
相關推薦
C++11有關執行緒同步的使用
互斥量和條件變數是控制執行緒同步的常用手段,用來保護多執行緒同時訪問的共享資料。 c++11提供了這些操作,同時還提供了原子變數和一次呼叫的操作,用起來非常的方便。 我們在這裡只介紹如何在C++中使用這些同步機制,有關概念的介紹我們就不在這裡多說了。
C++11 多執行緒同步
多執行緒能提高程式的效率,但同時也帶來了相應的問題----資料競爭。當多個執行緒同時操作同一個變數時,就會出現資料競爭。出現數據競爭,一般會用臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)、事件(Event)這四種方法來完
C++11多執行緒程式設計 第十章: 使用packaged_task優雅的讓同步函式非同步執行
C++11 Multithreading – Part 10: packaged_task<> Example and Tutorial Varun July 2, 2017 C++11 Multithreading – Part 10: packaged_tas
[轉]c++11 多執行緒 future/promise
[轉自 https://blog.csdn.net/jiange_zh/article/details/51602938] 1. < future >標頭檔案簡介 Classes std::future std::future_error std::packaged_task std::pro
c++11多執行緒 thread
1.thread建構函式 default (1) thread() noexcept; initialization (2) template <class Fn, class... Args> explicit
C++11 多執行緒執行緒共享資料
共享資料的問題 這些在作業系統中都有詳細的介紹,可以回顧作業系統課程。。很典型的就是資料競爭問題。 互斥量保護資料 最原始的方式:使用std::mutex建立互斥量,使用成員lock()加鎖,使用成員unlock()解鎖。但是這種方式需要我們在每個函數出口都呼叫一次unloc
基於C++11實現執行緒池的工作原理.
基於C++11實現執行緒池的工作原理. 文章目錄 基於C++11實現執行緒池的工作原理. 簡介 執行緒池的組成 1、執行緒池管理器 2、工作執行緒 3、任務介面, 4、任務佇列
基於C++11實現執行緒池的工作原理
基於C++11實現執行緒池的工作原理. 不久前寫過一篇執行緒池,那時候剛用C++寫東西不久,很多C++標準庫裡面的東西沒怎麼用,今天基於C++11重新實現了一個執行緒池。 簡介 執行緒池(thread pool):一種執行緒的使用模式,執行緒過多會帶來排程開銷,進而影響快取區域性性和整體效能。而執行緒池
c++11多執行緒與執行緒池
最近需要開發一個高效能運算庫,涉及到c++多執行緒的應用,上次做類似的事情已經是4年多以前了,印象中還頗有些麻煩。悔當初做了就算了,也沒想著留點記錄什麼的。這次又研究了一番,發現用上c++11特性之後,現在已經比較簡單了,在此記錄一下。 最簡單的多執行緒情況,不涉及公共變數,各個執行緒之間獨
利用C++11實現執行緒task的簡單封裝
#include <functional> #include <thread> #include <type_traits> /*Compile only if 'F' is callable. F maybe function, la
C++11多執行緒(1)
C++11中添加了duox多執行緒類,編寫C++程式可以直接使用C++11中的多執行緒庫,不必依賴於平臺多執行緒,這樣可以方便寫出誇平臺的多執行緒程式。多執行緒可以最大化利用計算機資源,提高程式碼的執行效率。 C++11中thread類提供兩
C++ 11 多執行緒下std::unique_lock與std::lock_guard的區別和用法
這裡主要介紹std::unique_lock與std::lock_guard的區別用法 先說簡單的 一、std::lock_guard的用法 std::lock_guard其實就是簡單的RAII封裝,在建構函式中進行加鎖,解構函式中進行解鎖,這樣可以保證函式退出時,鎖一定被釋放。 簡單來說,就是防止開
基於C++11的執行緒池
*需要C++11的支援,在vs2013下編譯通過 執行效果 背景 在傳統的收到任務即建立執行緒的情況下,我們每收到一個任務,就建立一個執行緒,執行任務,銷燬執行緒, 我們把這三個過程所用的時間分別記做T1,T2,T3 任務本身所用的時間僅佔T2/(T1+T2+T3),這在任務本身所用時間很短的情況下
基於C++11的執行緒池(threadpool),簡潔且可以帶任意多的引數(轉)
咳咳。C++11 加入了執行緒庫,從此告別了標準庫不支援併發的歷史。然而 c++ 對於多執行緒的支援還是比較低階,稍微高階一點的用法都需要自己去實現,譬如執行緒池、訊號量等。執行緒池(thread pool)這個東西,在面試上多次被問到,一般的回答都是:“管理一個任務佇列,一個執行緒佇列,然後每次取一個任務分
C++11多執行緒------std::async
std::async可以認為是封裝了一個std::promise,該函式返回一個std::future,用於獲取其他執行緒的資料。 一般有兩種模式: std::lanch::async:最常用的非同步模式,每次都要執行一遍 std::lanch::defer:只在第
C++11多執行緒---互斥量、鎖、條件變數的總結
關於互斥量std::mutex的總結 互斥量用於組成程式碼的臨界區。C++的多執行緒模型是基於記憶體的,或者說是基於程式碼片段的,這和我們作業系統學習的臨界區概念基本一致,但是與Golang不同,Golang是基於訊息模型的。 一個std::mutex的lock()和unlock
C++11多執行緒的原子操作
原子操作是同時只能有一個執行緒執行一個操作,不用使用互斥量即可實現,但是速度慢,而且一般只支援原生的型別,不夠靈活。更多的用處是作為訊號量進行使用。 示例程式碼,以int為例子: #include <atomic> #include <thread> #i
C++ 11 多執行緒--執行緒管理
說到多執行緒程式設計,那麼就不得不提並行和併發,多執行緒是實現併發(並行)的一種手段。並行是指兩個或多個獨立的操作同時進行。注意這裡是同時進行,區別於併發,在一個時間段內執行多個操作。在單核時代,多個執行緒是併發的,在一個時間段內輪流執行;在多核時代,多個執行緒可以實現真正的並行,在多核上真正獨立的並行執行。
C++11多執行緒程式設計 緒論及總結
C++11多執行緒程式設計 這一系列文章是從 https://thispointer.com/c11-multithreading-tutorial-series/ 轉過來的, 本來想翻譯一下, 但看了些內容, 用詞都不難, 讀英文沒有太大難度, 翻譯過來反而怕用詞不準畫蛇添
C++11多執行緒程式設計 第九章: std::async 更更優雅的寫多執行緒
C++11 Multithreading – Part 9: std::async Tutorial & Example Varun May 5, 2017 C++11 Multithreading – Part 9: std::async Tutorial &