和同學閒聊,談到多線程中的經典問題——生產者-消費者問題:要求實現兩個線程,一個線程負責對全局變量進行+1操作,一個線程負責打印更新後的值。自己從事code多年,自以爲對多線程瞭解深入,不假思索,寫出了下面的代碼:
#include <iostream>
#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>
static volatile bool g_flag = true; //線程運行標誌
static volatile int g_value = 0; //打印變量
static std::condition_variable g_cond; //條件變量
static std::mutex g_mutex; //互斥鎖
//打印線程入口函數
void print_thread(void)
{
printf("print thread enter \n");
while (g_flag) {
std::unique_lock<std::mutex> lk(g_mutex);
auto ret = g_cond.wait_for(lk, std::chrono::milliseconds(5000));
if (ret == std::cv_status::no_timeout) {
std::cout << "pppppppppp print thread " << g_value << std::endl;
} else {
printf("print thread wait timeout\n");
}
}
printf("print thread leave \n");
}
//計算線程入口函數
void add_thread(void)
{
printf("add thread enter \n");
while (g_value < 10) {
{
std::unique_lock<std::mutex> lk(g_mutex);
g_value++;
std::cout << "++++++++++ add thread " << g_value << std::endl;
}
g_cond.notify_one();
}
printf("add thread leave \n");
}
int main(int argc, char **argv)
{
std::thread thread_print(print_thread);
std::thread thread_add(add_thread);
getchar();
g_flag = false;
if (thread_print.joinable())
thread_print.join();
if (thread_add.joinable())
thread_add.join();
return 0;
}
運行之後,結果令我很尷尬:
分析原因,試着在分析線程調用g_cond.notify_one()之後添加等待,代碼如下:
void add_thread(void)
{
printf("add thread enter \n");
while (g_value < 10) {
{
std::unique_lock<std::mutex> lk(g_mutex);
g_value++;
std::cout << "++++++++++ add thread " << g_value << std::endl;
}
g_cond.notify_one();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
printf("add thread leave \n");
}
就這樣,問題解決了:
由此可知,是由於計算線程運行太快,而打印線程運行慢,造成condition_variable信號丟失導致。很明顯,要解決這樣的問題,在計算線程增加等待不是一個好的解決方案,因爲等待時間不好控制:時間太短,打印線程沒有處理完,仍會導致condition_variable信號丟失;時間太長,導致打印線程等待,造成時間浪費。新的方案,使用兩個condition_variable,一個用於等待打印,一個用於等待計算,代碼如下:
#include <iostream>
#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>
volatile bool g_flag = true; //運行標誌
volatile int g_value = 0; //變量
volatile bool g_print_able = false; //是否可以打印
std::condition_variable g_cond_add_enable; //計算條件變量
std::condition_variable g_cond_print_enable; //打印條件變量
std::mutex g_mutex;
//打印線程入口函數
void print_thread(void)
{
printf("print thread enter \n");
while (g_flag) {
{
std::unique_lock<std::mutex> lk(g_mutex);
g_cond_print_enable.wait(lk, [&] {return g_print_able; }); //等待打印
std::cout << "pppppppppp print thread " << g_value << std::endl;
g_print_able = false;
}
g_cond_add_enable.notify_one(); //通知計算
}
printf("print thread leave \n");
}
//計算線程入口函數
void add_thread(void)
{
printf("add thread enter \n");
while (g_value < 10) {
if (g_value != 0) {
std::unique_lock<std::mutex> lk(g_mutex);
g_cond_add_enable.wait(lk, [] {return !g_print_able; }); //等待計算
g_value++;
std::cout << "++++++++++ add thread " << g_value << std::endl;
g_print_able = true;
} else { //第一次計算,不等待,直接計算,否則造成死鎖
g_value++;
g_print_able = true;
std::cout << "++++++++++ add thread " << g_value << std::endl;
}
g_cond_print_enable.notify_one(); //通知打印
}
printf("add thread leave \n");
}
int main(int argc, char **argv)
{
std::thread thread_print(print_thread);
std::thread thread_add(add_thread);
getchar();
g_flag = false;
if (thread_print.joinable())
thread_print.join();
if (thread_add.joinable())
thread_add.join();
return 0;
}
打印結果如下:
當然,這種方案僅適用於生產者,消費者性能處理相當的情況。如果生產者,消費者處理性能相差較大,該種方案並不合適。