1. 程式人生 > >[C++11 併發程式設計] 11

[C++11 併發程式設計] 11

上一節,我們瞭解瞭如何對執行緒之間的共享資源進行保護的方法。但是,有些時候,我們需要線上程之間進行同步操作。一個執行緒等待另一個執行緒完成某項工作後,再繼續自己的工作。比如,某個執行緒需要等待一個訊息,或者某個條件變成true。接下來幾節,我們會看到如何使用C++標準庫來做到執行緒間同步。

等待另一個執行緒完成的方法有如下幾種:

  • 一個執行緒不停地查詢某個被mutex保護的貢獻資料區中的標誌的狀態。另一個執行緒在完成工作後,設定這個標誌。這種方法十分浪費資源,因為第一個執行緒需要不停的執行而佔用CPU。並且,當第一個執行緒鎖定mutex後,第二個執行緒即使完成了工作,也無法立即設定這個標誌,因為mutex被第一個執行緒鎖定了。
  • 第二種方法是在每次檢查表之後,執行一定時間的休眠。
bool flag;
std::mutex m;

void wait_for_flag()
{
  std::unique_lock<std::mutex> lk(m);
  while(!flag)
  {
    lk.unlock();  // 1 解鎖互斥量
    std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 2 休眠100ms
    lk.lock();   // 3 再鎖互斥量
  }
}
             比如,上面得了例子,解鎖mutex後,先睡眠100ms,在再次鎖定mutex。這樣做,比第一種方法要好一點,但是睡眠時間長短很難確定。短了,就跟沒有休眠一樣,很浪費CPU資源。長了,可能這個執行緒等待的另一個執行緒已經完成了操作,這個執行緒都還在休眠。可能會導致類似掉幀之類的問題。
  • 第三種方法是我們所推薦的,使用C++標準庫提供的機制-條件變數來實現等待操作。條件變數關聯到某種事件或者其它條件,一個或多個執行緒可以通過這個變數來等待條件的發生。某個執行緒在發現該條件滿足後,可以通知其它因等待該條件而掛起的執行緒,喚醒它們讓它們繼續執行。
C++標準庫提供了條件變數的兩種實現:std::condition_variable和std::condition_variable_any。它們都被宣告在<condition_variable>庫標頭檔案中。它們都需要和std::mutex配合工作而達到同步操作的目的,而區別在於,前者必須和std::mutex一起工作,而後者和可以和任何滿足特定需求的類似於mutex的模組一起工作。std::condition_variable_any更通用,但它需要更多的記憶體,對作業系統的效能和資源也有更大的影響。大多數情況下,std::condition_variable就夠了。 下面是一個使用std::condition_variable來實現執行緒間同步的例項。data_preparation_thread負責準備資料,接收使用者的輸入,一旦收到資料,通知data_processing_thread來進行處理。當用戶輸入q時,程式退出。
#include <iostream>

#include <mutex>
#include <condition_variable>
#include <thread>
#include <queue>

static bool more = true;

bool more_data_to_prepare()
{
	return more;
}

struct data_chunk
{
	char m_data = 'q';
	data_chunk(char c) : m_data(c) {
	}
};

data_chunk prepare_data()
{
	std::cout << "data_preparation_thread prepare_data"<< std::endl;
	char x = 'q';
	std::cin >> x;
	if (x == 'q')
	{
		more = false;
	}
    return data_chunk(x);
}

void process(data_chunk& data)
{
	std::cout << "process data: " << data.m_data << std::endl;
}

bool is_last_chunk(data_chunk& data)
{
	if (data.m_data == 'q')
    {
    	return true;
	}
	
	return false;
}

std::mutex mut;
std::queue<data_chunk> data_queue;	// 用於執行緒間通訊的佇列 
std::condition_variable data_cond;

void data_preparation_thread()
{
    while(more_data_to_prepare())
    {
    	std::cout << "data_preparation_thread while" << std::endl;
        data_chunk const data=prepare_data();
        std::lock_guard<std::mutex> lk(mut);
        // 資料準備好後,使用lock_guard來鎖定訊號量,將資料插入佇列之中 
        data_queue.push(data);
        std::cout << "data_preparation_thread notify_one" << std::endl;
        // 通過條件變數通知其它等待的執行緒 
        data_cond.notify_one();
    }
}

void data_processing_thread()
{
    while(true)
    {
    	std::cout << "data_processing_thread while" << std::endl;
    	// 使用unique_lock,因為我們需要在取得資料之後,處理資料之間,解鎖mutex 
        std::unique_lock<std::mutex> lk(mut);
        std::cout << "data_processing_thread before wait" << std::endl;
        // 等待條件滿足,unique_lock和Lambda函式,判斷資料佇列是否為空 
        data_cond.wait(lk,[]{return !data_queue.empty();});
        std::cout << "data_processing_thread pass wait" << std::endl;
        data_chunk data=data_queue.front();
        data_queue.pop();
        // 處理資料需要較多時間,所以先解鎖mutex 
        lk.unlock();
        std::cout << "data_processing_thread process data" << std::endl;
        process(data);
        if(is_last_chunk(data))
            break;
    }
}

int main()
{
	std::cout << "main" << std::endl;
    std::thread t1(data_preparation_thread);
    std::thread t2(data_processing_thread);
    
    t1.join();
    t2.join();
}
程式執行效果如下:輸入a,data_preparation_thread喚醒data_processing_thread,輸入q,data_processing_thread再次被data_preparation_thread喚醒,處理資料之後,程式結束。
main
data_preparation_thread while
data_preparation_thread prepare_data
data_processing_thread while
data_processing_thread before wait
a
data_preparation_thread notify_one
data_preparation_thread while
data_preparation_thread prepare_data
data_processing_thread pass wait
data_processing_thread process data
process data: a
data_processing_thread while
data_processing_thread before wait
q
data_preparation_thread notify_one
data_processing_thread pass wait
data_processing_thread process data
process data: q

--------------------------------
Process exited after 4.063 seconds with return value 0
Press any key to continue . . .



相關推薦

[C++11 併發程式設計] 11

上一節,我們瞭解瞭如何對執行緒之間的共享資源進行保護的方法。但是,有些時候,我們需要線上程之間進行同步操作。一個執行緒等待另一個執行緒完成某項工作後,再繼續自己的工作。比如,某個執行緒需要等待一個訊息,或者某個條件變成true。接下來幾節,我們會看到如何使用C++標準庫來做

C++11併發程式設計:原子操作atomic

一:概述   專案中經常用遇到多執行緒操作共享資料問題,常用的處理方式是對共享資料進行加鎖,如果多執行緒操作共享變數也同樣採用這種方式。   為什麼要對共享變數加鎖或使用原子操作?如兩個執行緒操作同一變數過程中,一個執行緒執行過程中可能被核心臨時掛起,這就是執行緒切換,當核心再次切換到該執行緒時,之前的資

C++11 併發程式設計指南——前言

開場白:前一段時間(大概在8月初)開始寫 《C++11 併發程式設計指南》(最早更新於:http://www.cnblogs.com/haippy),但是由於個人能力有限,加上 9 月初到現在一直在忙著找工作(革命尚未成功),精力有限,難免出現錯誤,希望讀者不吝指正。 另外,這是我在併發程式設

[C++11 併發程式設計] 07

假設有兩個執行緒,在執行某些操作時,都需要鎖定一對mutex,執行緒A鎖定了mutex A,而執行緒B鎖定了額mutex B,它們都在等待對方釋放另一個mutex,這就會導致這兩個執行緒都無法繼續執行。這種情況就是死鎖。 避免死鎖最簡單的方法是總是以相同的順序對兩個mute

C++11 併發程式設計基礎(一):併發、並行與C++多執行緒

正文 C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。另外,併發程式設計可提高應用的效能,這對對效能錙銖必較的C++程式設計師來說是值得關注的。 回到頂部 1. 何為併發 併發指的是兩個或多個獨立的活動在同

C++11 併發程式設計教程&學習筆記

C++11 併發程式設計教程&學習筆記 ​ 為了能夠編譯本文的示例程式碼,你需要有一個支援 C++11 的編譯器,筆者使用的是 TDM-GCC4.9.2 C++11 併發程式設計教程 - Part 1 : thread 初探

C++11併發程式設計(一)——初始C++11多執行緒庫

1 前言   C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。   在之前我們主要使用的多執行緒庫要麼

[C++11 併發程式設計] 17 超時等待

之前我們看到的所有等待機制都是不會超時的,也就是說,等待某個同步事件的執行緒會一直掛起。有些情況下,我們希望設定一個最長等待時間,使得程式可以繼續與使用者進行互動,使得使用者可以取消這個操作。我們先來看看C++11提供的時鐘類clock: clock clock提供瞭如下四

[C++11 併發程式設計] 15 承諾promise

假設有一個應用程式應用程式用於處理大量的網路連線,通常我們會為每一個連線建立單獨的處理執行緒。當執行緒數量較少時,這樣是可行的,但是隨著連線數量的增加,大量的執行緒需要消耗大量的系統資源。這樣,使用較少的執行緒,每個執行緒處理多個連線更為合適。 std::promise&l

[C++11 併發程式設計] 04

C++標準模板庫提供了一個輔助函式 - std::thread::hardware_concurrency(),通過這個函式,我們可以獲取應用程式可以真正併發執行的執行緒數量。下面這個例子,實現了一個併發版本的std::accumulate,它將工作拆分到多個執行緒中,為了

Java併發程式設計(11)-條件變數Condition的使用

文章目錄 一、併發程式設計中的條件變數 1.1、從生產者-消費者模型理解條件變數 1.2、Condition介面 1.3、Condition介面方法 二、實現一個生產者-消費

多執行緒高併發程式設計(11) -- 非阻塞演算法實現ConcurrentLinkedQueue原始碼分析

  一.背景   要實現對佇列的安全訪問,有兩種方式:阻塞演算法和非阻塞演算法。阻塞演算法的實現是使用一把鎖(出隊和入隊同一把鎖ArrayBlockingQueue)和兩把鎖(出隊和入隊各一把鎖LinkedBlockingQueue)來實現;非阻塞演算法使用自旋+CAS實現。   今天來探究下使用非阻塞演算法

併發程式設計(三): 使用C++11實現無鎖stack(lock-free stack)

C++11中CAS實現: template< class T> struct atomic { public: bool compare_exchange_weak( T& expected, T desired,                    

C++併發程式設計C++11

前言 首先需要說明,本部落格的主要內容參考自Forhappy && Haippy博主的分享,本人主要是參照博主的資料進行了學習和總結,並適當的衍生或補充了相關的其他知識內容。 C++11有了std::thread 以後,可以在語言層面編寫多執

c++併發程式設計實戰(C++11)pdf 高清

C++併發程式設計實戰PDF高清完整版下載。C++併發程式設計實戰PDF是一本非常熱門的電子圖書。這本書籍是由由威廉姆斯所著的,裡面擁有非常詳細的講解,對於新手來說是本不錯的書。 下載地址:http://download.csdn.net/download/l

C++11併發/多執行緒程式設計系列(2)

std::thread詳解 std::thread在標頭檔案<thread>中宣告,因此使用 std::thread 時需要包含 <thread>標頭檔案。 default(1) thread() noexcept;

併發程式設計-完美單例模式 時間:2018/11/1

class God { private static God instance = null; private static object locker = new object(); private God(){} //建構函式私有 public static Go

C++11 thread程式設計呼叫類方法並傳入引數

#include <thread> #include <iostream> class classA { public : classA() { std::cout<<" classA " <<std::endl; }

C++11併發學習之三:執行緒同步

1.<mutex> 標頭檔案介紹  (1)Mutex系列類(四種) std::mutex,最基本的 Mutex 類。 std::recursive_mutex,遞迴 Mutex 類。 std::time_mutex,定時 Mutex 類。 std::recursive_ti

C++11併發學習之一:小試牛刀

1.與C++11多執行緒相關的標頭檔案 C++11 新標準中引入了四個標頭檔案來支援多執行緒程式設計,他們分別是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。 <at