我正在看這段代碼:
#include <chrono>
#include <iostream>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
bool flag;
std::mutex m;
void wait_for_flag() {
// std::cout << &m << std::endl;
// return;
std::unique_lock<std::mutex> lk(m);
while (!flag) {
lk.unlock();
std::cout << "unlocked....." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "sleeping....." << std::endl;
lk.lock();
std::cout << "locked by " << std::this_thread::get_id() << "....."
<< std::endl;
}
}
int main(int argc, char const *argv[]) {
std::thread t(wait_for_flag);
std::thread t2(wait_for_flag);
std::thread t3(wait_for_flag);
std::thread t4(wait_for_flag);
std::thread t5(wait_for_flag);
t.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
我是新手,我認為互斥鎖只能由一個執行緒獲取。我有兩個問題:
- 為什么這些執行緒之間沒有死鎖,例如如果執行緒 A 運行 lk.unlock(),那么執行緒 B 運行 lk.lock(),然后執行緒 A 運行 lk.lock()。
- 這是什么意思我們在每個執行緒中定義一個新的 unique_lock 關聯到同一個互斥鎖(在這里稱為 m )
謝謝
uj5u.com熱心網友回復:
因為在獲得互斥鎖上的鎖之后,每個執行緒都會呼叫lk.unlock();,現在其他執行緒可以獲得互斥鎖上的鎖。只有當一個執行緒試圖鎖定一個已經鎖定的互斥鎖(由不同的執行緒)時,它才必須等待互斥鎖空閑。由于代碼中的任何執行緒最終都會呼叫lk.unlock();,因此不同的執行緒總是有機會獲得互斥鎖上的鎖,并且不會出現死鎖。
例如,如果您有兩個互斥體并且兩個執行緒嘗試以不同的順序鎖定它們,則會發生死鎖:
// thread A
std::unique_lock<std::mutex> lk1(mutex1);
std::unique_lock<std::mutex> lk2(mutex2); // X
// thread B
std::unique_lock<std::mutex> lk2(mutex2);
std::unique_lock<std::mutex> lk1(mutex1); // X
這里可能會發生執行緒 A 鎖定 mutex1,執行緒 B 鎖定 mutex2,然后都等待X另一個執行緒釋放另一個 mutex,但這永遠不會發生。這是一個僵局。
2.
鎖只是一種纖細的 RAII 型別。lock它的唯一目的是在創建和unlock銷毀時呼叫互斥鎖。您可以通過手動鎖定/解鎖互斥鎖來撰寫沒有鎖的相同代碼,但是當互斥鎖被鎖定時出現例外時,它將永遠不會被解鎖。
uj5u.com熱心網友回復:
@SolomonSlow 我的問題是,如果我們使用 unique_lock 將互斥鎖包裝在不同的執行緒中,為什么沒有死鎖......?
“死鎖”意味著有一些執行緒集,其中沒有一個執行緒可以繼續,直到該組的其他成員之一執行某些操作。在最簡單的死鎖中,只有兩個執行緒,并且有兩個互斥鎖:
- 執行緒 A
unique_lock在互斥鎖 1 上放置了一個鎖,它被阻塞,等待在互斥鎖 2 上放置一個鎖。 - 執行緒 B 在 mutex 2 上加了鎖,它被阻塞,等待在 mutex 1 上加鎖。
在執行緒 B 先做某事之前,執行緒 A 不能做任何事情,而在執行緒 A 先做某事之前,執行緒 B 也不能做任何事情。兩個執行緒都無法再做任何事情。僵局。
如果沒有執行緒等待的至少兩個不同的事物(例如,兩個不同的互斥體),您就不會出現死鎖。如果只有一個互斥鎖,那么無論哪個執行緒將其鎖定,該執行緒都將能夠繼續。當沒有執行緒能夠繼續時,這只是一個死鎖。
在您的示例中,五個執行緒中的每一個都進入一個回圈:
- 解鎖互斥鎖,
- 列印,睡眠,列印,
- 鎖定互斥鎖,
- 列印,
- 回到回圈的頂部。
每當您的一個執行緒鎖定互斥鎖時,沒有什么可以阻止它列印然后回傳頂部并再次解鎖互斥鎖,以便其他執行緒可以運行。沒有僵局。
uj5u.com熱心網友回復:
這不是一個答案。這只是一個插圖。我把你的一個例子變成了三個不同的例子,它們都達到了相同的結果。我希望它可以幫助您更好地理解unique_lock它的作用。
第一種方法根本不用unique_lock。它只使用mutex. 這是老派的方式——在RAII被發現之前我們做事的方式。
std::mutex m;
{
...
while (...) {
do_work_outside_critical_section();
m.lock(); // explicitly put a "lock" on the mutex.
do_work_inside_critical_section();
m.unlock(); // explicitly remove the "lock."
}
}
老式的方法是有風險的,因為如果do_work_inside_critical_section()拋出例外,它將使互斥鎖處于鎖定狀態,并且任何嘗試再次鎖定它的執行緒可能會永遠掛起。
第二種方式使用unique_lock,這是RAII的一個體現。
RAII 模式確保沒有辦法離開這個在 mutex 上留下鎖的代碼塊m。unique_lock無論如何,解構式總是會被呼叫,并且解構式會移除鎖。
std::mutex m;
{
...
while (...) {
do_work_outside_critical_section();
std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
do_work_inside_critical_section();
} // destructor implicitly removes the "lock."
}
請注意,在此版本中,unique_lock每次回圈都會構造和銷毀 a。這聽起來可能很昂貴,但實際上并非如此。unique_lock旨在以這種方式使用。
最后一種方法是您在示例中所做的。它只創建和銷毀unique_lock一次,然后在回圈內反復鎖定和解鎖它。這可行,但它的代碼行比上面的版本多,這使得閱讀和理解有點困難。
std::mutex m;
{
...
std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
while (...) {
lk.unlock(); // explicitly remove the "lock" from the mutex.
do_work_outside_critical_section();
lk.lock(); // explicitly put a "lock" back on the mutex.
do_work_inside_critical_section();
}
} // destructor implicitly removes the "lock."
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/513380.html
標籤:C 多线程c 11
上一篇:如果std::reference_wrapper沒有operator =,std::reference_wrapper<int>如何使用operator =?
下一篇:COALESCE的目的是什么=0
