【C++】多線程與條件變量【三】

1 條件變量是什麼?

condition_variable 類是同步原語,能用於阻塞一個線程,或同時阻塞多個線程,直至另一線程修改共享變量(條件)並通知 condition_variable

有意修改變量的線程必須

  1. 獲得 std::mutex (常通過 std::lock_guard
  2. 在保有鎖時進行修改
  3. std::condition_variable 上執行 notify_onenotify_all (不需要爲通知保有鎖)

即使共享變量是原子的,也必須在互斥下修改它,以正確地發佈修改到等待的線程。

任何有意在 std::condition_variable 上等待的線程必須

  1. 在與用於保護共享變量者相同的互斥上獲得 std::unique_lock<std::mutex>
  2. 執行下列之一:

std::condition_variable 只可與 std::unique_lock<std::mutex> 一同使用;此限制在一些平臺上允許最大效率。 std::condition_variable_any 提供可與任何基本可鎖定 (BasicLockable) 對象,例如 std::shared_lock 一同使用的條件變量。

condition_variable 容許 waitwait_forwait_untilnotify_onenotify_all 成員函數的同時調用。

std::condition_variable標準佈局類型 (StandardLayoutType) 。它非可複製構造 (CopyConstructible) 可移動構造 (MoveConstructible) 可複製賦值 (CopyAssignable) 可移動賦值 (MoveAssignable)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uCh6xXA1-1609602482375)(C:\Users\guoqi\AppData\Roaming\Typora\typora-user-images\1609594667734.png)]

實例1:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
	// 等待直至 main() 發送數據
	std::unique_lock<std::mutex> lk(m);
	cv.wait(lk, [] {return ready; });

	// 等待後,我們佔有鎖。
	std::cout << "Worker thread is processing data\n";
	data += " after processing";

	// 發送數據回 main()
	processed = true;
	std::cout << "Worker thread signals data processing completed\n";

	// 通知前完成手動解鎖,以避免等待線程才被喚醒就阻塞(細節見 notify_one )
	lk.unlock();
	cv.notify_one();
}

int main()
{
	std::thread worker(worker_thread);

	data = "Example data";
	// 發送數據到 worker 線程
	{
		std::lock_guard<std::mutex> lk(m);
		ready = true;
		std::cout << "main() signals data ready for processing\n";
	}
	cv.notify_one();

	// 等候 worker
	{
		std::unique_lock<std::mutex> lk(m);
		cv.wait(lk, [] {return processed; });
	}
	std::cout << "Back in main(), data = " << data << '\n';

	worker.join();
	getchar();
}

在這裏插入圖片描述

2 條件變量本質?

條件變量是操作系統實現的。關鍵在於理解爲啥要有它,而且需注意一點,條件變量自身並不包含條件。因爲它通常和 if (或者while) 一起用,所以叫條件變量。

併發有兩大需求,一是互斥,二是等待。互斥是因爲線程間存在共享數據,等待則是因爲線程間存在依賴。

條件變量,是爲了解決等待需求。考慮實現生產者消費者隊列,生產者和消費者各是一個線程。一個明顯的依賴是,消費者線程依賴生產者線程 push 元素進隊列。

典型的如CS架構, 基於請求響應的模式,即客戶端向發送服務器請求,然後服務器把請求結果返回給客戶端。

而服務器可以用生產者消費者模型來處理客戶端的請求:

  • 服務器有一些線程(或者進程),把客戶端的請求轉換成統一格式壓入消息隊列,這些線程稱爲生產者。
  • 另有一些線程(或者進程),不斷從消息隊列取出消息來處理,這些線程稱爲消費者。

線程同步的原理和實現使用互斥鎖解決數據競爭訪問問題,算是線程同步的加鎖原語,用於排他性的訪問共享數據。我們在使用mutex時,一般都會期望加鎖不要阻塞,總是能立刻拿到鎖,然後儘快訪問數據,用完之後儘快解鎖,這樣才能不影響併發性和性能。

**條件變量是線程的另外一種有效同步機制。**這些同步對象爲線程提供了交互的場所(一個線程給另外的一個或者多個線程發送消息),我們指定在條件變量這個地方發生,一個線程用於修改這個變量使其滿足其它線程繼續往下執行的條件,其它線程則等待接收條件已經發生改變的信號。當條件變量同互斥鎖一起使用時,條件變量允許線程以一種無競爭的方式等待任意條件的發生。


3 引入條件變量的原因?

前一章【C++】多線程與互斥鎖【二】介紹了多線程併發訪問共享數據時遇到的數據競爭問題,通過互斥鎖保護共享數據,保證多線程對共享數據的訪問同步有序。但如果一個線程需要等待一個互斥鎖的釋放,該線程通常需要輪詢該互斥鎖是否已被釋放,我們也很難找到適當的輪訓週期,如果輪詢週期太短則太浪費CPU資源,如果輪詢週期太長則可能互斥鎖已被釋放而該線程還在睡眠導致發生延誤。

針對典型的如CS架構,如果沒有條件變量,你會怎麼實現消費者呢?讓消費者線程一直輪詢隊列(需要加 mutex)。如果是隊列裏有值,就去消費;如果爲空,要麼是繼續查( spin 策略),要麼 sleep 一下,讓系統過一會再喚醒你,你再次查。可以想到,無論哪種策略,都不通用,要麼費 cpu,要麼線程過分 sleep,影響該線程的性能。有條件變量後,就可以使用事件模式了。上面的消費者線程,發現隊列爲空,就告訴操作系統,我要 wait,一會肯定有其他線程發信號來喚醒我的。這個『其他線程』,實際上就是生產者線程。生產者線程 push 隊列之後,則調用 signal,告訴操作系統,之前有個線程在 wait,你現在可以喚醒它了。**上述兩種等待方式,前者是輪詢(poll),後者是事件(event)。**一般來說,事件方式比較通用,性能不會太差(但存在切換上下文的開銷)。輪詢方式的性能,就非常依賴併發 pattern,也特別消耗 cpu。

實例2:

如下給出一個實例:一個線程往隊列中放入數據,一個線程從隊列中提取數據,取數據前需要判斷一下隊列中確實有數據,由於這個隊列是線程間共享的,所以,需要使用互斥鎖進行保護,一個線程在往隊列添加數據的時候,另一個線程不能取,反之亦然。程序實現代碼如下:

//cond_var1.cpp用互斥鎖實現一個生產者消費者模型

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>

std::deque<int> q;						//雙端隊列標準容器全局變量
std::mutex mu;							//互斥鎖全局變量
//生產者,往隊列放入數據
void function_1() {
    int count = 10;
    while (count > 0) {
        std::unique_lock<std::mutex> locker(mu);
        q.push_front(count);			//數據入隊鎖保護
        locker.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));		//延時1秒
        count--;
    }
}
//消費者,從隊列提取數據
void function_2() {
    int data = 0;
    while ( data != 1) {
        std::unique_lock<std::mutex> locker(mu);
        if (!q.empty()) {			//判斷隊列是否爲空
            data = q.back();
            q.pop_back();			//數據出隊鎖保護
            locker.unlock();
            std::cout << "t2 got a value from t1: " << data << std::endl;
        } else {
            locker.unlock();
        }
    }
}

int main() {
    std::thread t1(function_1);
    std::thread t2(function_2);
    t1.join();
    t2.join();

    getchar();
    return 0;
}

在這裏插入圖片描述

在生產過程中,因每放入一個數據有1秒延時,所以這個生產的過程是很慢的;在消費過程中,存在着一個while循環,只有在接收到表示結束的數據的時候,纔會停止,每次循環內部,都是先加鎖,判斷隊列不空,然後就取出一個數,最後解鎖。所以說,在1s內,做了很多無用功!這樣的話,CPU佔用率會很高,可能達到100%(單核)
在這裏插入圖片描述

實例3:

由於消費者在while循環內因等待數據做了過多的無用功導致CPU佔有率過高,我們可以考慮在消費者發現隊列爲空時,讓消費者小睡一會兒,即增加一個小延時(比如500ms),相當於增大了輪詢間隔週期,應該能降低CPU的佔用率。按該方案修改後的消費者代碼如下:

//cond_var1.cpp用互斥鎖實現一個生產者消費者模型
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>

std::deque<int> q;						//雙端隊列標準容器全局變量
std::mutex mu;							//互斥鎖全局變量
										//生產者,往隊列放入數據
void function_1() {
	int count = 10;
	while (count > 0) {
		std::unique_lock<std::mutex> locker(mu);
		q.push_front(count);			//數據入隊鎖保護
		locker.unlock();
		std::this_thread::sleep_for(std::chrono::seconds(1));//延時1秒
		std::cout << "t1 sent a value from t1: " << count << std::endl;
		count--;
	}
}
//消費者,從隊列提取數據
void function_2() {
	int data = 0;
	while (data != 1) {
		std::unique_lock<std::mutex> locker(mu);
		if (!q.empty()) {			//判斷隊列是否爲空
			data = q.back();
			q.pop_back();			//數據出隊鎖保護
			locker.unlock();
			std::cout << "t2 got a value from t1: " << data << std::endl;
		}
		else {
			locker.unlock();
		}
	}
}

//消費者,從隊列提取數據
void function_22() {
	int data = 0;
	while (data != 1) {
		std::unique_lock<std::mutex> locker(mu);
		if (!q.empty()) {			//判斷隊列是否爲空
			data = q.back();
			q.pop_back();			//數據出隊鎖保護
			locker.unlock();
			std::cout << "t2 got a value from t1: " << data << std::endl;
		}
		else {
			locker.unlock();
			std::this_thread::sleep_for(std::chrono::milliseconds(500));//延時500毫秒
		}
	}
}


int main() {
	std::thread t1(function_1);
	std::thread t2(function_22);
	t1.join();
	t2.join();

	getchar();
	return 0;
}

在這裏插入圖片描述
前面也說了,困難之處在於如何確定這個延長時間(即輪詢間隔週期),如果間隔太短會過多佔用CPU資源,如果間隔太長會因無法及時響應造成延誤。

這就引入了條件變量來解決該問題:條件變量使用“通知—喚醒”模型,****生產者生產出一個數據後通知消費者使用,消費者在未接到通知前處於休眠狀態節約CPU資源;當消費者收到通知後,趕緊從休眠狀態被喚醒來處理數據,使用了事件驅動模型,在保證不誤事兒的情況下儘可能減少無用功降低對資源的消耗。

實例4:

由於有很多線程對消息隊列進行操作,所以我們需要用鎖來保證隊列操作的正確。我們當然可以使用一個互斥體,在生產者線程壓入消息,或消費者線程彈出消息時使用它,這樣就保證了線程同步。但是這裏存在一個效率問題,消費者線程只是循環獲得鎖,然後判斷消息隊列是否有消息。大多數時候隊列可能是沒有消息的,這樣就比較浪費運算了。

最好的情況是這樣的,消費者線程判斷隊列沒有消息後,進入休眠狀態,直到有別的線程告訴它有消息了才醒過來,此時消費者繼續取消息來處理。條件變量就能滿足這樣的要求,pthread的條件變量API是這樣的:

// 初始化一個條件變量
int pthread_cond_init(pthread_cond_t *restrict cond,
    const pthread_condattr_t *restrict attr);
// 釋放一個條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
// 等待條件滿足
int pthread_cond_wait(pthread_cond_t *restrict cond,
    pthread_mutex_t *restrict mutex);
// 喚醒至少一個等待條件的線程
int pthread_cond_signal(pthread_cond_t *cond);
// 喚醒所有等待條件的線程
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 消費者調用pthread_cond_wait等待條件滿足,這裏的條件滿足就是隊列中有消息。
  • 生產者壓入消息後,調用pthread_cond_signal或pthread_cond_broadcast通知消費者。
  • 條件變量只是用於通知,但它不是鎖,所以生產者或消費者仍然需要用互斥體來保護消息隊列,這就是爲什麼條件變量需要和互斥體一起使用的原因。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

// 消息結構
struct msg {
    struct msg *next;
    int data;       // 消息數據
};

struct msg *queue;  // 消息隊列
pthread_cond_t qcond = PTHREAD_COND_INITIALIZER;    // 簡化初始化條件變量和互斥體
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

// 隨機數範圍[mi, ma]
int randint(int mi, int ma) {
    double r = (double)rand() * (1.0 / ((double)RAND_MAX + 1.0));
    r *= (double)(ma - mi) + 1.0;
    return (int)r + mi;
}

// 打印消息
void print_msg(struct msg *m) {
    printf(">>>>msg: %d\n", m->data);
}

// 壓入消息
void push_msg(struct msg *m) {
    pthread_mutex_lock(&qlock);
    m->next = queue;
    queue = m;
    pthread_mutex_unlock(&qlock);
    // 通知條件滿足
    pthread_cond_signal(&qcond);
}

// 生產者線程:
void* product(void *data) {
    while (1) {
        usleep(randint(1000*100, 1000*200));
        struct msg *m = malloc(sizeof(*m));
        memset(m, 0, sizeof(*m));
        m->data = randint(0, 1000);
        push_msg(m);
    }
}

// 彈出消息
struct msg* pop_msg() {
    struct msg *m;
    pthread_mutex_lock(&qlock);
    // 等待條件滿足
    while (queue == NULL) pthread_cond_wait(&qcond, &qlock);
    m = queue;
    queue = m->next;
    pthread_mutex_unlock(&qlock);
    return m;
}

// 消費者線程
void* consum(void *data) {
    whlie (1) {
        struct msg *m = pop_msg();
        print_msg(m);
        free(m);
    }
}

int main() {
#define PRO_NUM 3
#define CON_NUM 3
    pthread_t tid_p[PRO_NUM];
    pthread_t tid_c[CON_NUM];

    int i;
    for (i = 0; i < PRO_NUM; ++i) {
        pthread_create(&tid_p[i], NULL, product, NULL);
    }
    for (i = 0; i < CON_NUM; ++i) {
        pthread_create(&tid_c[i], NULL, consum, NULL);
    }


    for (i = 0; i < PRO_NUM; ++i) {
        pthread_join(tid_p[i], NULL);
    }
    for (i = 0; i < CON_NUM; ++i) {
        pthread_join(tid_c[i], NULL);
    }
    return 0;
}
  • 程序創建了幾個生產者線程和消費者線程,一個條件變量和一個互斥體。

  • 生產者不斷通過push_msg向queue壓入消息,注意這裏使用了互斥體(qlock),因爲有多個線程在生產消息和消費消息,所以必須使用qlock保護消息隊列。當消息壓入完成後,調用pthread_cond_signal通知消費者有消息了。

  • 消費者不斷通過pop_msg從queue取出消息,它也是通過pthread_mutex_lock先加鎖,成功獲得鎖後,有一個while循環: c while (queue == NULL) pthread_cond_wait(&qcond, &qlock); 這裏有兩個地方要解釋一下:

    • 爲什麼pthread_cond_wait需要傳入qlock?因爲前面已經獲得了鎖,所以在線程進入休眠之前,pthread_cond_wait要先解鎖。如果pthread_cond_wait裏面不先解鎖,該線程進入休眠狀態,此時其他的消費者或生產者調用pthread_mutex_lock也進入休眠狀態,那麼他們永遠也等不到解鎖的時刻,這時就出現死鎖的情況了。接着描述:解鎖後線程進入休眠,某個生產者線程成功獲得鎖,向隊列壓入消息,然後調用pthread_cond_signal;此時等待的消費者醒過來,它馬上又調用pthread_mutex_lock嘗試獲得鎖,獲得鎖後纔可以從消息隊列取出消息來處理。這就是pthread_cond_wait做的事情:先解鎖,然後休眠,得到通知後醒來再加鎖。
    • 爲什麼要用while循環判斷queue是否爲空?這是因爲消費者有多個,pthread_cond_signal可能會喚醒多個消費者,假如A先獲得了鎖,從隊列取出了消息,然後解鎖;B接着獲得了鎖,但隊列已經空了,所以需要用while循環判斷,隊列不爲空才往下處理。

4 如何使用條件變量?

4.1 std::condition_variable

Condition variable作用:一個可以阻塞線程的對象,直至被告知繼續。

A condition variable is an object able to block the calling thread until notified to resume.

It uses a unique_lock (over a mutex) to lock the thread when one of its wait functions is called. The thread remains blocked until woken up by another thread that calls a notification function on the same condition_variable object.

當它的一個等待函數被調用時,它使用unique_lock(通過互斥鎖)來鎖定線程。該線程保持阻塞狀態,直到被另一個線程喚醒,該線程調用同一個condition_variable對象上的通知函數。
condition_variable類型的對象總是使用unique_lock來等待:對於任何類型的可鎖定類型,請參閱condition_variable_any

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8GtVvFgq-1609602482383)(C:\Users\guoqi\AppData\Roaming\Typora\typora-user-images\1609595732375.png)]

實例5:

// condition_variable example
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
	std::unique_lock<std::mutex> lck(mtx);
	while (!ready) cv.wait(lck);
	// ...
	std::cout << "thread " << id << '\n';
}

void go() {
	std::unique_lock<std::mutex> lck(mtx);
	ready = true;
	cv.notify_all();
}

int main()
{
	std::thread threads[10];
	// spawn (生產) 10 threads:
	for (int i = 0; i<10; ++i)
		threads[i] = std::thread(print_id, i);

	std::cout << "10 threads ready to race...\n";
	go();                       // go!

	for (auto& th : threads) th.join();
	getchar();
	return 0;
}

4.2 std::condition_variable_any

Condition variable (any lock)

Same as condition_variable, except that its wait functions can take any lockable type as argument (condition_variable objects can only take unique_lock). Other than that, they are identical.

與condition_variable相同,不同的是它的等待函數可以接受任何可鎖定類型作爲參數(condition_variable對象只能接受unique_lock)。除此之外,它們是相同的。
在這裏插入圖片描述

實例6:

C++標準庫在< condition_variable >中提供了條件變量,藉由它,一個線程可以喚醒一個或多個其他等待中的線程。原則上,條件變量的運作如下:

  • 你必須同時包含< mutex >和< condition_variable >,並聲明一個mutex和一個condition_variable變量;
  • 那個通知“條件已滿足”的線程(或多個線程之一)必須調用notify_one()或notify_all(),以便條件滿足時喚醒處於等待中的一個條件變量;
  • 那個等待"條件被滿足"的線程必須調用wait(),可以讓線程在條件未被滿足時陷入休眠狀態,當接收到通知時被喚醒去處理相應的任務;

將上面的實例2,實例3程序使用條件變量解決輪詢間隔難題的示例代碼如下:

//cond_var2.cpp用條件變量解決輪詢間隔難題

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>

std::deque<int> q;						//雙端隊列標準容器全局變量
std::mutex mu;							//互斥鎖全局變量
std::condition_variable cond;           //全局條件變量
										//生產者,往隊列放入數據
void function_13() {
	int count = 10;
	while (count > 0) {
		std::unique_lock<std::mutex> locker(mu);
		q.push_front(count);			//數據入隊鎖保護
		locker.unlock();

		cond.notify_one();              // 向一個等待線程發出“條件已滿足”的通知

		std::this_thread::sleep_for(std::chrono::seconds(1));//延時1秒
		std::cout << "t1 sent a value from t1: " << count << std::endl;
		count--;
	}
}
//消費者,從隊列提取數據
void function_23() {
	int data = 0;
	while (data != 1) {
		std::unique_lock<std::mutex> locker(mu);

		while (q.empty())        //判斷隊列是否爲空
			cond.wait(locker); // 解鎖互斥量並陷入休眠以等待通知被喚醒,被喚醒後加鎖以保護共享數據

		data = q.back();
		q.pop_back();			//數據出隊鎖保護
		locker.unlock();
		std::cout << "t2 got a value from t1: " << data << std::endl;
	}
}

int main() {
	std::thread t1(function_13);
	std::thread t2(function_23);
	t1.join();
	t2.join();

	getchar();
	return 0;
}

使用條件變量對CPU的佔用率也很低,而且免去了輪詢間隔該設多長的難題!

上面的代碼有三個注意事項:

  1. 在function_23中,在判斷隊列是否爲空的時候,使用的是while(q.empty()),而不是if(q.empty()),這是因爲wait()從阻塞到返回,不一定就是由於notify_one()函數造成的,還有可能由於系統的不確定原因喚醒(可能和條件變量的實現機制有關),這個的時機和頻率都是不確定的,被稱作僞喚醒。如果在錯誤的時候被喚醒了,執行後面的語句就會錯誤,所以需要再次判斷隊列是否爲空,如果還是爲空,就繼續wait()阻塞;
  2. 在管理互斥鎖的時候,使用的是std::unique_lock而不是std::lock_guard,而且事實上也不能使用std::lock_guard。這需要先解釋下wait()函數所做的事情,可以看到,在wait()函數之前,使用互斥鎖保護了,如果wait的時候什麼都沒做,豈不是一直持有互斥鎖?那生產者也會一直卡住,不能夠將數據放入隊列中了。所以,wait()函數會先調用互斥鎖的unlock()函數,然後再將自己睡眠,在被喚醒後,又會繼續持有鎖,保護後面的隊列操作。lock_guard沒有lock和unlock接口,而unique_lock提供了,這就是必須使用unique_lock的原因;
  3. 使用細粒度鎖,儘量減小鎖的範圍,在notify_one()的時候,不需要處於互斥鎖的保護範圍內,所以在喚醒條件變量之前可以將鎖unlock()。

實例7:

還可以將cond.wait(locker)換一種寫法,wait()的第二個參數可以傳入一個函數表示檢查條件,這裏使用lambda函數最爲簡單,如果這個函數返回的是true,wait()函數不會阻塞會直接返回,如果這個函數返回的是false,wait()函數就會阻塞着等待喚醒,如果被僞喚醒,會繼續判斷函數返回值。代碼示例如下:

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>

std::deque<int> q;						//雙端隊列標準容器全局變量
std::mutex mu;							//互斥鎖全局變量
std::condition_variable cond;           //全局條件變量
										//生產者,往隊列放入數據
void function_14() {
	int count = 10;
	while (count > 0) {
		std::unique_lock<std::mutex> locker(mu);
		q.push_front(count);			//數據入隊鎖保護
		locker.unlock();

		cond.notify_one();              // 向一個等待線程發出“條件已滿足”的通知

		std::this_thread::sleep_for(std::chrono::seconds(1));//延時1秒
		std::cout << "t1 sent a value from t1: " << count << std::endl;
		count--;
	}
}
//消費者,從隊列提取數據
void function_23() {
	int data = 0;
	while (data != 1) {
		std::unique_lock<std::mutex> locker(mu);

		while (q.empty())        //判斷隊列是否爲空
			cond.wait(locker); // 解鎖互斥量並陷入休眠以等待通知被喚醒,被喚醒後加鎖以保護共享數據

		data = q.back();
		q.pop_back();			//數據出隊鎖保護
		locker.unlock();
		std::cout << "t2 got a value from t1: " << data << std::endl;
	}
}
//消費者,從隊列提取數據
void function_24() {
	int data = 0;
	while (data != 1) {
		std::unique_lock<std::mutex> locker(mu);

		//如果條件變量被喚醒,檢查隊列非空條件是否爲真,爲真則直接返回,爲假則繼續等待
		cond.wait(locker, []() { return !q.empty(); });

		data = q.back();
		q.pop_back();			//數據出隊鎖保護
		locker.unlock();
		std::cout << "t2 got a value from t1: " << data << std::endl;
	}
}

int main() {
	std::thread t1(function_14);
	std::thread t2(function_24);
	t1.join();
	t2.join();

	getchar();
	return 0;
}

4.3 std::condition_variable::wait

Wait until notified 等到通知

當前線程(應該已經鎖定了lck的互斥鎖)的執行會被阻塞,直到收到通知。

在阻塞線程的那一刻,函數自動調用lock .unlock(),允許其他鎖定的線程繼續。

一旦得到通知(由其他線程顯式地發出),該函數將解除阻塞並調用lck.lock(),使lck處於與函數被調用時相同的狀態。然後函數返回(注意最後的互斥鎖在返回之前可能會再次阻塞線程)。

通常,函數會被另一個線程中的notify_one或notify_all成員的調用喚醒。但某些實現可能會在不調用任何這些函數的情況下產生虛假的喚醒調用。因此,使用該功能的用戶應確保滿足其恢復的條件。

如果指定了pred(2),函數只有在pred返回false時纔會阻塞,並且只有當它變爲true時,通知才能解除線程阻塞(這對於檢查虛假的喚醒調用特別有用)。這個版本(2)的行爲就像實現了:

實例8:

while (!pred()) wait(lck);
// condition_variable::wait (with predicate)
#include <iostream>           // std::cout
#include <thread>             // std::thread, std::this_thread::yield
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;
bool shipment_available() {return cargo!=0;}

void consume (int n) {
  for (int i=0; i<n; ++i) {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck,shipment_available);
    // consume:
    std::cout << cargo << '\n';
    cargo=0;
  }
}

int main ()
{
  std::thread consumer_thread (consume,10);

  // produce 10 items when needed:
  for (int i=0; i<10; ++i) {
    while (shipment_available()) std::this_thread::yield();
    std::unique_lock<std::mutex> lck(mtx);
    cargo = i+1;
    cv.notify_one();
  }

  consumer_thread.join();
  getchar();	
  return 0;
}

在這裏插入圖片描述

實例9:

ailable() {return cargo!=0;}

void consume (int n) {
  for (int i=0; i<n; ++i) {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck,shipment_available);
    // consume:
    std::cout << cargo << '\n';
    cargo=0;
  }
}

int main ()
{
  std::thread consumer_thread (consume,10);

  // produce 10 items when needed:
  for (int i=0; i<10; ++i) {
    while (shipment_available()) std::this_thread::yield();
    std::unique_lock<std::mutex> lck(mtx);
    cargo = i+1;
    cv.notify_one();
  }

  consumer_thread.join();
  getchar();	
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章