【C++】多线程互斥锁、条件变量
我們了解互斥量和條件變量之前,我們先來看一下為什么要有互斥量和條件變量這兩個東西,了解為什么有這兩東西之后,理解起來后面的東西就簡單很多了!!!
先來看下面這段簡單的代碼:
int g_num = 0;void print(int id)
{for (int i = 0; i < 5; i++){++g_num;cout << "id = " << id << "==>" << g_num << endl;std::this_thread::sleep_for(std::chrono::seconds(1));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}
上述代碼功能大致就是在線程tha和thb中運行函數print,每個線程對g_num進行加加一次,最后加出來的g_num的值應該是10,那么我們現在來看結果:
我們看到運行結果,為什么打印結果最后,按理來說兩個線程各加五次,最后結果應該是10呀,怎么會是9呢?
?如上圖所示,是因為++這個運算符不是原子操作(不會被線程調度機制打斷的操作),我們可以將g_num設置為原子數,改為atomic_int g_num = 0;
atomic_int g_num = 0; //將g_num設置為原子操作數
//atomic<int> g_num = 0;這個和上面是一樣的 下面這行是模板化之后的void print(int id)
{for (int i = 0; i < 5; i++){++g_num;cout << "id = " << id << "==>" << g_num << endl;std::this_thread::sleep_for(std::chrono::seconds(1));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}
將g_num設置為原子操作數之后,在++階段就不會被線程調度機制給打斷,我們來看運行結果:
運行結果是我所期望的但是中間那塊又出了一點小狀況連著打著兩個4,兩個6,這種情況該怎么辦呢?
下面就該說道我們的互斥鎖了:
互斥鎖:
? ? ? ? 在上述代碼中我們使用了共享資源----->全局量g_num,兩個線程同時對g_num進行++操作,為了保護共享資源,在線程里也有這么一把鎖——互斥鎖(mutex),互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態,即上鎖( lock )和解鎖( unlock )。
具體的互斥鎖概念在我另外一篇博客中講過Linux中互斥鎖和條件變量的概念:Linux多線程的同步-----信號量和互斥鎖_神廚小福貴!的博客-CSDN博客前面兩篇給基本概念講過了,大家有興趣的可以去看一下:Linux多線程_神廚小福貴!的博客-CSDN博客進程和線程的區別有哪些呢?進程是資源分配的最小單位,線程是CPU調度的最小單位進程有自己的獨立地址空間,線程共享進程中的地址空間進程的創建消耗資源大,線程的創建相對較小進程的切換開銷大,線程的切換開銷相對較小進程:程序執行的過程叫進程。線程:進程內部的一條執行序列或執行路徑,一個進程可以包含多條線程(多線程)!每個進程最少有一個線程,例如下面代碼:#include <stdio.h>int mahttps://blog.csdn.net/qq_45829112/article/details/121524904
來看下面代碼:
int g_num = 0;
std::mutex mtx; //創建鎖對象void print(int id)
{for (int i = 0; i < 5; i++){mtx.lock(); //上鎖++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock(); //解鎖std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}
我們來看運行結果:符合我們最初的預期。
?打開官方文檔,可以看到
創建鎖對象只有這個方法,拷貝構造被刪除了?。
std::mutex::try_lock
? ? ? ? 對于互斥鎖的lock和unlock我們都很熟悉了,下面來說一下std::mutex::try_lock這個成員函數!
?try_lock字面意思就是說嘗試上鎖,如果上鎖成功,返回true,上鎖失敗則返回false,但是如果上鎖失敗,他還是會接著往下運行,不會像lock哪運被阻塞在上鎖那塊,所以try_lock必須得在循環中使用:
int g_num = 0;
std::mutex mtx;void print(int id)
{for (int i = 0; i < 5; i++){mtx.try_lock(); //代碼只是將lock換成了try_lock且沒把try_lock扔在循環中執行++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}
我們來看運行結果:
?unlock of? unowned? mutex,這玩意思就是說你在給個沒上鎖的互斥鎖解鎖,所以報這錯誤,因此try_lock擱在普通語句中,會有很大的問題,現命我們演示一下將這玩意放到循環中去弄一邊:
int g_num = 0;
std::mutex mtx;void print(int id)
{for (int i = 0; i < 5; i++){while (!mtx.try_lock()) //try_lock失敗時為false 前面加了!,所以失敗時為true 然后打印嘗試加鎖{ //然后再次嘗試加鎖,只有加鎖成功了,才能出這個while循環cout << "try lock" << endl;} ++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}
我們來看運行結果:
?運行結果符合我們的預期,但是try_lock這個函數有個不好處是太損耗資源了,當它加鎖失敗時,一直嘗試加鎖一直嘗試加鎖,損耗CPU資源。
條件變量:condition_variable
????????
框住這三個函數較為重要,下面著重來說下面這三個函數:?這里順便說一下,下面代碼將會是條件變量和互斥鎖的結合使用,至于為什么要將互斥鎖和條件變量一起使用,原因就是互斥鎖狀態太單一了,而條件變量允許阻塞,接收信號量等剛好彌補了互斥鎖的缺陷所以這些一起使用!!!
? ? ? ? 這三個函數呢,通過一個小實驗來實現,通過多線程分別打印123一直到100:
std::mutex mtx;
std::condition_variable cv;
int isReady = 0;
const int n = 100;void print_A()
{std::unique_lock<mutex> lock(mtx); //unique_lock相當于線程中的智能制造 自動解鎖,不需要再unlockint i = 0;while (i < n){while (isReady != 0) {cv.wait(lock);//互斥鎖和信號量一起使用 wait參數為鎖對象}cout << "A" ;isReady = 1;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all(); //當isReady等于0時print_B 和 print_C 處于阻塞狀態 //該函數就是喚醒所有等待的函數,然后通過isReady來進行判斷要進行那個函數的運行}
}void print_B()
{std::unique_lock<mutex> lock(mtx); //unique_lock相當于線程中的智能制造 自動解鎖,不需要再unlockint i = 0;while (i < n){while (isReady != 1){cv.wait(lock);}cout << "B" ;isReady = 2;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all();}
}void print_C()
{std::unique_lock<mutex> lock(mtx); //unique_lock相當于線程中的智能制造 自動解鎖,不需要再unlockint i = 0;while (i < n){while (isReady != 2){cv.wait(lock);}cout << "C" ;isReady = 0;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all();}
}int main()
{thread tha(print_A);thread thb(print_B);thread thc(print_C);tha.join();thb.join();thc.join();return 0;
}
上面代碼解析:
運行結果:
我們可以看到上述代碼最后喚醒其他線程使用的是notify_all()函數,notify_all()函數作用就是環球其他阻塞的函數,然后因為isready這個數的存在,所以就會選擇合適的線程來進行執行,如果我們使用notify_one()呢,先來說下notify_one()函數的作用是什么。notify_one()函數是喚醒其他線程(隨機喚醒,這道題中不適合,因為如果打印完A之后喚醒了C線程那么就會一直阻塞在那塊)
我們試一下notify_one()函數,可以發現這玩意確實會堵塞在那塊:
總結
以上是生活随笔為你收集整理的【C++】多线程互斥锁、条件变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万能的天涯,有人知道这双鞋的品牌么!!!
- 下一篇: 【C++】二叉树的先序、中序、后序遍历序