真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C++11中線程、鎖和條件變量的介紹

這篇文章主要介紹“C++11中線程、鎖和條件變量的介紹”,在日常操作中,相信很多人在C++11中線程、鎖和條件變量的介紹問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”C++11中線程、鎖和條件變量的介紹”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

專注于為中小企業(yè)提供成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)鳳慶免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了千余家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

std::thread類代表了一個(gè)可執(zhí)行的線程,它來自頭文件。與其它創(chuàng)建線程的API(比如 Windows API中的CreateThread)不同的是, 它可以使用普通函數(shù)、lambda函數(shù)以及仿函數(shù)(實(shí)現(xiàn)了operator()函數(shù)的類)。另外,它還允許向線程函數(shù)傳遞任意數(shù)量的參數(shù)。

#include  void func()  { // do some work } int main()  {     std::thread t(func);     t.join(); return 0;  }

在上面的例子中,t是一個(gè)線程對(duì)象,函數(shù)func()運(yùn)行于該線程之中。調(diào)用join函數(shù)后,該調(diào)用線程(本例中指的就是主線程)就會(huì)在join進(jìn)來進(jìn)行執(zhí)行的線程t結(jié)束執(zhí)行之前,一直處于阻塞狀態(tài)。如果該線程函數(shù)執(zhí)行結(jié)束后返回了一個(gè)值,該值也將被忽略。不過,該函數(shù)可以接受任意數(shù)量的參數(shù)。

void func(int i, double d, const std::string& s)  {      std::cout << i << ", " << d << ", " << s << std::endl;  } int main()  {     std::thread t(func, 1, 12.50, "sample");     t.join(); return 0;  }

盡管我們可以向線程函數(shù)傳遞任意數(shù)量的參數(shù),但是,所有的參數(shù)都是按值傳遞的。如果需要將參數(shù)按引用進(jìn)行傳遞,那么就一定要象下例所示一樣,把該參數(shù)封裝到 std::ref或者std::cref之中。

void func(int& a)  {     a++;  } int main()  { int a = 42;     std::thread t(func, std::ref(a));     t.join();        std::cout << a << std::endl; return 0;  }

上面程序打印結(jié)果為43,但要不是將a封裝到std::ref之中的話,輸出的將是42。

除join方法之外,這個(gè)線程類還提供了另外幾個(gè)方法:

  • swap: 將兩個(gè)線程對(duì)象的底層句柄進(jìn)行交換

  • detatch: 允許執(zhí)行該方法的線程獨(dú)立于本線程對(duì)象的執(zhí)行而繼續(xù)執(zhí)行。脫離后的線程就再也不能執(zhí)行join了(你不能等待到它執(zhí)行結(jié)束了)

int main() {      std::thread t(funct);      t.detach(); return 0;  }

有一點(diǎn)非常重要,值得注意:線程函數(shù)中要是拋出了異常的話,使用通常的try-catch方式是捕獲不到該異常的。換句話說,下面這種做法行不通:

try {      std::thread t1(func);      std::thread t2(func);         t1.join();      t2.join();  } catch(const std::exception& ex)  {      std::cout << ex.what() << std::endl;  }

要在線程間傳遞異常,你可以先在線程函數(shù)中捕獲它們,然后再將它們保存到一個(gè)合適的地方,隨后再讓另外一個(gè)線程從這個(gè)地方取得這些異常。

std::vector  g_exceptions; void throw_function()  { throw std::exception("something wrong happened");  } void func()  { try {        throw_function();     } catch(...)     {        std::lock_guard lock(g_mutex);        g_exceptions.push_back(std::current_exception());     }  } int main()  {     g_exceptions.clear();      std::thread t(func);     t.join(); for(auto& e : g_exceptions)     { try { if(e != nullptr)           {              std::rethrow_exception(e);           }        } catch(const std::exception& e)        {           std::cout << e.what() << std::endl;        }     } return 0;  }

要獲得更多關(guān)于捕獲并傳遞異常的知識(shí),你可以閱讀在主線程中處理工作線程拋出的C++異常以及怎樣才能在線程間傳遞異常?。

在深入討論之前還有一點(diǎn)值得注意,頭文件里還在命名空間std::this_thread中提供了一些輔助函數(shù):

  • get_id: 返回膽怯線程的id

  • yield: 讓調(diào)度器先運(yùn)行其它的線程,這在忙于等待狀態(tài)時(shí)很有用

  • sleep_for: 將當(dāng)前線程置于阻塞狀態(tài),時(shí)間不少于參數(shù)所指定的時(shí)間段

  • sleep_util: 在指定的時(shí)刻來臨前,一直將當(dāng)前的線程置于阻塞狀態(tài)

在上一個(gè)例子中,我需要對(duì)g_exceptions這個(gè)vector進(jìn)行同步訪問,以確保同一個(gè)時(shí)刻只能有一個(gè)線程向其中壓入新元素。為了實(shí)現(xiàn)同步,我使用了一個(gè)互斥量,并在該互斥量上進(jìn)行了鎖定?;コ饬渴且粋€(gè)核心的同步原語(yǔ),C++11的頭文件中包含了四種不同的互斥量。

  • mutex: 提供了核心的lock()函數(shù)和unlock()函數(shù),以及非阻塞式的try_lock()方法,該方法在互斥量不可用時(shí)會(huì)立即返回。

  • recursive_mutex: 運(yùn)行在同一線程中,多次獲得同一個(gè)互斥量。

  • timed_mutex: 同第一條中的mutex類似,但它還帶來了另外兩個(gè)方法try_lock_for()和try_lock_until(),分別用于在某個(gè)時(shí)間段內(nèi)或在某個(gè)時(shí)刻到來之前獲得該互斥量。

  • recursive_timed_mutex: 結(jié)合了第二條的timed_mutex和第三條的recusive_mutex。

以下所列就是一個(gè)使用std::mutex(注意其中g(shù)et_id()和sleep_for()這兩個(gè)前文所述的輔助函數(shù)的用法)的例子。

#include   #include   #include   #include      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(rand() % 10));      std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;         g_lock.unlock();  } int main()  {      srand((unsigned int)time(0));         std::thread t1(func);      std::thread t2(func);      std::thread t3(func);         t1.join();      t2.join();      t3.join(); return 0;  }

其輸出將類似如下所示:

entered thread 10144 leaving thread 10144 entered thread 4188 leaving thread 4188 entered thread 3424 leaving thread 3424

lock()和unlock()這兩個(gè)方法顧名思義,頭一個(gè)方法用來對(duì)互斥量進(jìn)行加鎖,如果互斥量不可得便會(huì)處于阻塞狀態(tài);第二個(gè)方法用來對(duì)互斥量進(jìn)行解鎖。

接下來的這個(gè)例子演示的是一個(gè)簡(jiǎn)單的線程安全的容器(內(nèi)部使用的是std::vector)。這個(gè)容器具有添加單個(gè)元素的add()方法以及添加一批元素的addrange()方法,addrange()方法內(nèi)只是簡(jiǎn)單的調(diào)用了add()方法。

template  class container   {      std::mutex _lock;      std::vector _elements; public: void add(T element)       {          _lock.lock();          _elements.push_back(element);          _lock.unlock();      } void addrange(int num, ...)      {          va_list arguments;             va_start(arguments, num); for (int i = 0; i < num; i++)          {              _lock.lock();              add(va_arg(arguments, T));              _lock.unlock();          }             va_end(arguments);       } void dump()      {          _lock.lock(); for(auto e : _elements)              std::cout << e << std::endl;          _lock.unlock();      }  }; void func(container& cont)  {      cont.addrange(3, rand(), rand(), rand());  } int main()  {      srand((unsigned int)time(0));         container cont;         std::thread t1(func, std::ref(cont));      std::thread t2(func, std::ref(cont));      std::thread t3(func, std::ref(cont));         t1.join();      t2.join();      t3.join();         cont.dump(); return 0;  }

這個(gè)程序執(zhí)行起來會(huì)進(jìn)入死鎖狀態(tài)。其原因在于,該容器多次嘗試獲取同一個(gè)互斥量而之前卻并沒有釋放該互斥量,這么做是行不通的。這正是std::recursive_mutex的用武之地,它允許同一個(gè)線程多次獲得同一個(gè)互斥量,可重復(fù)獲得的最大次數(shù)并未具體說明,但一旦查過一定次數(shù),再對(duì)lock進(jìn)行調(diào)用就會(huì)拋出std::system錯(cuò)誤。為了修復(fù)上面所列代碼的死鎖問題(不通過修改addrange方法的實(shí)現(xiàn),讓它不對(duì)lock和unlock方法進(jìn)行調(diào)用),我們可以將互斥量改為std::recursive_mutex。

template  class container   {      std::recursive_mutex _lock; // ...   };

經(jīng)過修改之后,該程序的輸出會(huì)同如下所示類似:

6334 18467 41 6334 18467 41 6334 18467 41

明眼的讀者可能已經(jīng)發(fā)現(xiàn)了,每次調(diào)用func()所產(chǎn)生的數(shù)字序列都完全相同。這是因?yàn)閷?duì)srad的初始化是要分線程進(jìn)行的,對(duì)srand()的調(diào)用只是在主線程中進(jìn)行了初始化。在其它的工作線程中,srand并沒有得到初始化,所以每次產(chǎn)生的數(shù)字序列就是完全相同的了。

顯式的加鎖和解鎖可能會(huì)導(dǎo)致一定的問題,比如忘了解鎖或者加鎖的順序不對(duì)都有可能導(dǎo)致死鎖。本標(biāo)準(zhǔn)提供了幾個(gè)類和函數(shù)用于幫助解決這類問題。使用這些封裝類就能夠以相互一致的、RAII風(fēng)格的方式使用互斥量了,它們可以在相應(yīng)的代碼塊的范圍內(nèi)進(jìn)行自動(dòng)的加鎖和解鎖動(dòng)作。這些封裝類包括:

  • lock_guard: 該類的對(duì)象在構(gòu)造之時(shí)會(huì)試圖獲得互斥量的擁有權(quán)(通過調(diào)用lock()實(shí)現(xiàn)),而在析構(gòu)之時(shí)會(huì)自動(dòng)釋放它所獲得的互斥量(通過調(diào)用unlock()實(shí)現(xiàn))。這是一個(gè)不可復(fù)制的類。

  • unique_lock: 是一個(gè)通用的互斥量封裝類。與lock_quard不同,它還支持延遲加鎖、時(shí)間鎖、遞歸鎖、鎖所有權(quán)的轉(zhuǎn)移并且還支持使用條件變量。這也是一個(gè)不可復(fù)制的類,但它是可以移動(dòng)的類。

使用這些封裝類,我們可以象這樣來改寫我們的容器:

template  class container   {      std::recursive_mutex _lock;      std::vector _elements; public: void add(T element)       {          std::lock_guard locker(_lock);          _elements.push_back(element);      } void addrange(int num, ...)      {          va_list arguments;             va_start(arguments, num); for (int i = 0; i < num; i++)          {              std::lock_guard locker(_lock);              add(va_arg(arguments, T));          }             va_end(arguments);       } void dump()      {          std::lock_guard locker(_lock); for(auto e : _elements)              std::cout << e << std::endl;      }  };

有人會(huì)說,既然dump()方法并不會(huì)對(duì)容器的狀態(tài)做出任何修改,所以它應(yīng)該定義為congst的方法。但要是你真的這么改了之后,編譯器就會(huì)報(bào)告出如下的錯(cuò)誤:

‘std::lock_guard<_Mutex>::lock_guard(_Mutex &)' : cannot convert parameter 1 from ‘const std::recursive_mutex' to ‘std::recursive_mutex &'

互斥量(無論使用的是哪一種實(shí)現(xiàn))必須要獲得和釋放,這就意味著要調(diào)用非常量型的lock()和unlock()方法。所以,從邏輯上講,lock_guard不能在定義中添加const(因?yàn)樵摲椒ǘx為const的話,互斥量也就必需是const的了)這個(gè)問題有個(gè)解決辦法,可以讓 mutex變?yōu)閙utable的。成為 mutable之后就可以在const函數(shù)中對(duì)狀態(tài)進(jìn)行修改了。不過,這種用法應(yīng)該只用于隱藏的或者“元”狀態(tài)(比如,對(duì)計(jì)算結(jié)果或者查詢到的數(shù)據(jù)進(jìn)行緩存,以供下次調(diào)用時(shí)直接使用而無需再次計(jì)算或查詢;再比如,對(duì) 只是對(duì)對(duì)象的實(shí)際狀態(tài)起著輔助作用的互斥量中的位進(jìn)行修改)。

template  class container   {     mutable std::recursive_mutex _lock;     std::vector _elements; public: void dump() const {        std::lock_guard locker(_lock); for(auto e : _elements)           std::cout << e << std::endl;     }  };

這些封裝類都具有可以接受一個(gè)用來指導(dǎo)加鎖策略的參數(shù)的構(gòu)造器,可用的加鎖策略有:

  • defer_lockof typedefer_lock_t: 不要取得互斥量的擁有權(quán)

  • try_to_lockof typetry_to_lock_t: 在不會(huì)被阻塞的情況下嘗試獲得互斥量的擁有權(quán)

  • adopt_lockof typeadopt_lock_t: 假設(shè)調(diào)用線程已經(jīng)獲得了互斥量的擁有權(quán)

這些策略的定義如下所示:

struct defer_lock_t { };   struct try_to_lock_t { };   struct adopt_lock_t { };   constexpr std::defer_lock_t defer_lock = std::defer_lock_t();   constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t();   constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t();

除了這些互斥量的封裝類,本標(biāo)準(zhǔn)還提供了幾個(gè)用來對(duì)一個(gè)或多個(gè)互斥量進(jìn)行加鎖的方法。

  • lock: 使用一種可避免死鎖的算法對(duì)互斥量進(jìn)行加鎖(通過調(diào)用tolock()、try_lock()以及unlock())。

  • try_lock: 通過調(diào)用try_lock()i按照參數(shù)里指定的互斥量的順序?qū)Χ鄠€(gè)互斥量進(jìn)行加鎖。

這里舉一個(gè)造成死鎖的例子:我們有一個(gè)保存元素的容器,還有一個(gè)叫做exchange()的方法,用來將一個(gè)元素從一個(gè)容器中取出來放入另外一個(gè)容器。為了成為線程安全的函數(shù),這個(gè)函數(shù)通過獲得每個(gè)容器的互斥量,對(duì)兩個(gè)容器的訪問進(jìn)行了同步處理。

template  class container   { public:      std::mutex _lock;      std::set _elements; void add(T element)       {          _elements.insert(element);      } void remove(T element)       {          _elements.erase(element);      }  }; void exchange(container& cont1, container& cont2, int value)  {      cont1._lock.lock();      std::this_thread::sleep_for(std::chrono::seconds(1)); // <-- forces context switch to simulate the deadlock  cont2._lock.lock();             cont1.remove(value);      cont2.add(value);         cont1._lock.unlock();      cont2._lock.unlock();  }

假設(shè)這個(gè)函數(shù)是從兩個(gè)不同的線程中進(jìn)行調(diào)用的,在第一個(gè)線程中有一個(gè)元素從第一個(gè)容器中取出來,放到了第二個(gè)容器中,在第二個(gè)線程中該元素又從第二個(gè)容器中取出來放回到了第一個(gè)容器中。這樣會(huì)導(dǎo)致死鎖(如果線程上下文正好在獲得第一個(gè)鎖的時(shí)候從一個(gè)線程切換到了另一個(gè)線程的時(shí)候就會(huì)發(fā)生死鎖)。

int main()  {      srand((unsigned int)time(NULL));         container cont1;       cont1.add(1);      cont1.add(2);      cont1.add(3);         container cont2;       cont2.add(4);      cont2.add(5);      cont2.add(6);         std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 3);      std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 6);         t1.join();      t2.join(); return 0;  }

要解決該問題,你可以使用以能夠避免死鎖的方式獲得鎖的std::lock:

void exchange(container& cont1, container& cont2, int value)  {      std::lock(cont1._lock, cont2._lock);          cont1.remove(value);      cont2.add(value);         cont1._lock.unlock();      cont2._lock.unlock();  }

條件變量

C++11還提供了對(duì)另外一個(gè)同步原語(yǔ)的支持,這個(gè)原語(yǔ)就是條件變量。使用條件變量可以將一個(gè)或多個(gè)線程進(jìn)入阻塞狀態(tài),直到收到另外一個(gè)線程的通知,或者超時(shí)或者發(fā)生了虛假喚醒,才能退出阻塞狀態(tài)。頭文件中包含的條件變量有兩種實(shí)現(xiàn):

  • condition_variable: 要求任何想等待該條件變量的線程必需先獲得std::unique_lock鎖。

  • condition_variable_any: 該實(shí)現(xiàn)更加通用,它可以用于任何滿足基本條件的鎖(只要實(shí)現(xiàn)了lock()和unlock()方法即可)。因?yàn)樗褂闷饋泶鷥r(jià)要更高一些(從性能和操作系統(tǒng)的字樣的角度講),所以,應(yīng)該在只有它所提供的額外的靈活性是必不可少的情況下才會(huì)選用它。

下面說說條件變量的工作原理:

  • 必須至少要有一個(gè)等待條件變?yōu)閠rue的線程。等待中的線程必須首先獲得一個(gè)unique_lock鎖。 該鎖將會(huì)傳遞給wait()方法,然后wait()方法會(huì)釋放互斥量并將該線程暫停,直到條件變量得到相應(yīng)的信號(hào)。當(dāng)接受到信號(hào),線程被喚醒后,該鎖就又被重新獲得了。

  • 必須至少要有一個(gè)線程發(fā)送信號(hào)使得條件變?yōu)閠rue。信號(hào)可以通過調(diào)用notify_one()來發(fā)送,發(fā)用這個(gè)方法發(fā)送后就會(huì)將處于阻塞狀態(tài)的等待該條件獲得信號(hào)的線程中的某一個(gè)線程(任意一個(gè)線程)恢復(fù)執(zhí)行;還可以通過調(diào)用notify_all()將等待該條件的所以線程喚醒。

  • 因?yàn)樵诙嗵幚砥鞯沫h(huán)境下,要讓條件喚醒成為完全可預(yù)測(cè)會(huì)有一些復(fù)雜情況難以克服,所以就會(huì)出現(xiàn)一些虛假喚醒。也就是說,線程甚至在沒有人向條件變量發(fā)送信號(hào)的情況下就有可能會(huì)被喚醒。因此,在線程喚醒后,仍然需要檢測(cè)條件是不是還為true。而且因?yàn)樘摷賳拘芽赡軙?huì)多次發(fā)生,所以該檢測(cè)必須用一個(gè)循環(huán)來進(jìn)行。

以下代碼給出了一個(gè)利用狀態(tài)變量來同步線程的例子:幾個(gè)工作線程可能在他們運(yùn)行的時(shí)候產(chǎn)生錯(cuò)誤并且他們把這些錯(cuò)誤放到隊(duì)列里面。一個(gè)記錄線程會(huì)通過從隊(duì)列得到并輸出錯(cuò)誤來處理這些錯(cuò)誤代碼。當(dāng)有錯(cuò)誤發(fā)生的時(shí)候,工作線程會(huì)發(fā)信號(hào)給記錄線程。記錄線程一直在等待著狀態(tài)變量接收信號(hào)。為了防止虛假的喚醒,所以記錄線程的等待是發(fā)生在一個(gè)以檢測(cè)布爾值(boolean)的循環(huán)之中的。

#include   #include   #include   #include   #include   #include    std::mutex              g_lockprint;  std::mutex              g_lockqueue;  std::condition_variable g_queuecheck;  std::queue         g_codes; bool g_done; bool g_notified; void workerfunc(int id, std::mt19937& generator)  { // print a starting message  {          std::unique_lock locker(g_lockprint);          std::cout << "[worker " << id << "]\trunning..." << std::endl;      } // simulate work  std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); // simulate error  int errorcode = id*100+1;      {          std::unique_lock locker(g_lockprint);          std::cout  << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;      } // notify error to be logged  {          std::unique_lock locker(g_lockqueue);          g_codes.push(errorcode);          g_notified = true;          g_queuecheck.notify_one();      }  } void loggerfunc()  { // print a starting message  {          std::unique_lock locker(g_lockprint);          std::cout << "[logger]\trunning..." << std::endl;      } // loop until end is signaled  while(!g_done)      {          std::unique_lock locker(g_lockqueue); while(!g_notified) // used to avoid spurious wakeups  {              g_queuecheck.wait(locker);          } // if there are error codes in the queue process them  while(!g_codes.empty())          {              std::unique_lock locker(g_lockprint);              std::cout << "[logger]\tprocessing error:  " << g_codes.front()  << std::endl;              g_codes.pop();          }           g_notified = false;      }  } int main()  { // initialize a random generator  std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); // start the logger  std::thread loggerthread(loggerfunc); // start the working threads  std::vector threads; for(int i = 0; i < 5; ++i)      {          threads.push_back(std::thread(workerfunc, i+1, std::ref(generator)));      } // work for the workers to finish  for(auto& t : threads)          t.join(); // notify the logger to finish and wait for it  g_done = true;      loggerthread.join(); return 0;  }  Running this code produces an output that looks like this (notice this output is different with each run because each worker thread works, i.e. sleeps, for a random interval):  [logger]        running...  [worker 1]      running...  [worker 2]      running...  [worker 3]      running...  [worker 4]      running...  [worker 5]      running...  [worker 1]      an error occurred: 101 [worker 2]      an error occurred: 201 [logger]        processing error: 101 [logger]        processing error: 201 [worker 5]      an error occurred: 501 [logger]        processing error: 501 [worker 3]      an error occurred: 301 [worker 4]      an error occurred: 401 [logger]        processing error: 301 [logger]        processing error: 401

如上所示的wait()方法有兩個(gè)重載:

1.一個(gè)是只有一個(gè)唯一鎖;這個(gè)重載釋放鎖,封鎖線程和把線程加入都是等待這一個(gè)狀態(tài)變量的線程隊(duì)列里面;當(dāng)狀態(tài)變量被信號(hào)通知后或者是一個(gè)假喚醒發(fā)生,這些線程就會(huì)被喚醒。但他們中任何一個(gè)發(fā)生時(shí),鎖就被重新獲得然后函數(shù)返回。

2.另外一個(gè)是對(duì)于唯一鎖的添加,它也是使用一個(gè)循環(huán)的謂語(yǔ)直到它返回false;這個(gè)重載可以用來防止假式喚醒。它基本上是與以下是等價(jià)的:

while(!predicate())      wait(lock);

因此在上例中,通過使用重載的wait函數(shù)以及一個(gè)驗(yàn)證隊(duì)列狀態(tài)(空或不空)的斷言,就可以避免使用布爾變量g_notified了:

void workerfunc(int id, std::mt19937& generator)  { // print a starting message  {          std::unique_lock locker(g_lockprint);          std::cout << "[worker " << id << "]\trunning..." << std::endl;      } // simulate work  std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); // simulate error  int errorcode = id*100+1;      {          std::unique_lock locker(g_lockprint);          std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;      } // notify error to be logged  {          std::unique_lock locker(g_lockqueue);          g_codes.push(errorcode);          g_queuecheck.notify_one();      }  } void loggerfunc()  { // print a starting message  {          std::unique_lock locker(g_lockprint);          std::cout << "[logger]\trunning..." << std::endl;      } // loop until end is signaled  while(!g_done)      {          std::unique_lock locker(g_lockqueue);           g_queuecheck.wait(locker, [&](){return !g_codes.empty();}); // if there are error codes in the queue process them  while(!g_codes.empty())          {              std::unique_lock locker(g_lockprint);              std::cout << "[logger]\tprocessing error:  " << g_codes.front() << std::endl;              g_codes.pop();          }      }  }

除了這個(gè)wait()重載方法,還有另外兩個(gè)進(jìn)行類似重載的等待方法,都有用了一個(gè)用來避免虛假喚醒的斷言:

  • wait_for: 在條件變量收到信號(hào)或者指定的超時(shí)發(fā)生前,一直都將線程置于阻塞狀態(tài)。

  • wait_until: 在條件變量收到信號(hào)或者指定的時(shí)刻到來前,一直都將線程處于阻塞狀態(tài)。

這兩個(gè)函數(shù)不帶斷言的重載函數(shù)會(huì)返回一個(gè)cv_status狀態(tài),該狀態(tài)用來表明線程被喚醒了到底是因?yàn)榘l(fā)生了超時(shí)還是因?yàn)闂l件變量收到了信號(hào)抑或是發(fā)生了虛假喚醒。

本標(biāo)準(zhǔn)還提供了一個(gè)叫做notified_all_at_thread_exit的函數(shù),它實(shí)現(xiàn)了一種機(jī)制,在該機(jī)制下,我們可以通知其它線程,某個(gè)給定的線程執(zhí)行結(jié)束了,并銷毀了所有的thread_local對(duì)象。之所以引入該函數(shù),是因?yàn)槿绻褂昧藅hread_local后,采用join()之外的機(jī)制等待線程可能會(huì)導(dǎo)致不正確甚至是致命的行為,出現(xiàn)這樣的問題是因?yàn)?thread_local的析構(gòu)函數(shù)甚至可能會(huì)在原本處于等待中的線程繼續(xù)執(zhí)行后被執(zhí)行了而且還可能已經(jīng)執(zhí)行完成了。(有關(guān)這方面更多的情況可參見N3070和N2880)。 一般情況下,notified_all_at_thread_exitTypically必須正好在線程生成前調(diào)用。下面給出一個(gè)例子,演示一下 notify_all_at_thread_exit是如何同condition_variable一起使用來對(duì)兩個(gè)線程進(jìn)行同步處理的:

std::mutex              g_lockprint;  std::mutex              g_lock;  std::condition_variable g_signal; bool g_done; void workerfunc(std::mt19937& generator)  {     {        std::unique_lock locker(g_lockprint);        std::cout << "worker running..." << std::endl;     }      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {        std::unique_lock locker(g_lockprint);        std::cout << "worker finished..." << std::endl;     }      std::unique_lock lock(g_lock);     g_done = true;     std::notify_all_at_thread_exit(g_signal, std::move(lock));  } int main()  { // initialize a random generator  std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());      std::cout << "main running..." << std::endl;      std::thread worker(workerfunc, std::ref(generator));     worker.detach();      std::cout << "main crunching..." << std::endl;      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {        std::unique_lock locker(g_lockprint);        std::cout << "main waiting for worker..." << std::endl;     }      std::unique_lock lock(g_lock); while(!g_done) // avoid spurious wake-ups  g_signal.wait(lock);      std::cout << "main finished..." << std::endl; return 0;  }

如果工作線程是在主線程結(jié)束之前結(jié)束的,輸出將會(huì)是如下所示:

main running...  worker running...  main crunching...  worker finished...  main waiting for worker...  main finished...

如果是主線程在工作線程結(jié)束之前結(jié)束的,輸出將會(huì)是如下所示:

main running...  worker running...  main crunching...  main waiting for worker...  worker finished...  main finished...

結(jié)束語(yǔ)

C++11標(biāo)準(zhǔn)使得C++開發(fā)人員能夠以一種標(biāo)準(zhǔn)的和平臺(tái)獨(dú)立的方式來編寫多線程代碼。本文一一講述了標(biāo)準(zhǔn)所支持的線程和同步機(jī)制。頭文件提供了名為thread的類(另外還包含了一些輔助類或方法),該類代表了一個(gè)執(zhí)行線程。頭文件提供了幾種互斥量的實(shí)現(xiàn),以及對(duì)線程進(jìn)行同步訪問的封裝類。頭文件為條件變量提供了兩種實(shí)現(xiàn),利用這些實(shí)現(xiàn)可以讓一個(gè)或多個(gè)線程進(jìn)入阻塞狀態(tài),直到從收到來自另外一個(gè)或多個(gè)線程的通知、或者發(fā)生超時(shí)或虛假喚醒為止才會(huì)被喚醒。推薦在這方面再閱讀一些別的資料來獲得更詳細(xì)的信息。

到此,關(guān)于“C++11中線程、鎖和條件變量的介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!


文章標(biāo)題:C++11中線程、鎖和條件變量的介紹
標(biāo)題來源:http://weahome.cn/article/gjosod.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部