為練習c++ 線程同步,做了LeeCode 1114題. 按序打印:
給你一個類:
public class Foo {public void first() { print("first"); }public void second() { print("second"); }public void third() { print("third"); } }
三個不同的線程 A、B、C 將會共用一個?Foo
?實例。
- 線程 A 將會調用?
first()
?方法 - 線程 B 將會調用?
second()
?方法 - 線程 C 將會調用?
third()
?方法
請設計修改程序,以確保?second()
?方法在?first()
?方法之后被執行,third()
?方法在?second()
?方法之后被執行。
提示:
- 盡管輸入中的數字似乎暗示了順序,但是我們并不保證線程在操作系統中的調度順序。
- 你看到的輸入格式主要是為了確保測試的全面性。
示例 1:
輸入:nums = [1,2,3] 輸出:"firstsecondthird" 解釋: 有三個線程會被異步啟動。輸入 [1,2,3] 表示線程 A 將會調用 first() 方法,線程 B 將會調用 second() 方法,線程 C 將會調用 third() 方法。正確的輸出是 "firstsecondthird"。
示例 2:
輸入:nums = [1,3,2] 輸出:"firstsecondthird" 解釋: 輸入 [1,3,2] 表示線程 A 將會調用 first() 方法,線程 B 將會調用 third() 方法,線程 C 將會調用 second() 方法。正確的輸出是 "firstsecondthird"。
提示:
nums
?是?[1, 2, 3]
?的一組排列
答案&測試代碼:
?
#include <iostream>
#include "listNode.h"
#include "solution.h"
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "solution3.h"
#include "dataDefine.h"
#include "uthash.h"
#include "IntArrayList.h"
#include <string.h>
#include <thread>
#include <atomic>
#include "DemoClass.h"
#include <mutex>
#include <condition_variable>
#include <functional>void printFirst() {std::cout << "first";
}void printSecond() {std::cout << "second";
}void printThird() {std::cout << "third";
}void testLeeCode1114_() {class Foo { // 函數內部也可以定義類。private:std::mutex mtx; // 互斥鎖std::condition_variable cv; // 條件變量bool isFirstDone;bool isSecondDone;public:Foo() {this->isFirstDone = false;this->isSecondDone = false;}void first(function<void()> printFirst) {{ // 加一對{}是為了限制下面加鎖的作用域。離開作用域lock_guard自動立即釋放鎖// lock_guard構造時立即加鎖(如果鎖被占用會等待鎖釋放,一旦鎖釋放就搶占)。不支持手動釋放鎖。lock_guard 在析構時會自動釋放鎖。std::lock_guard<std::mutex> lock(mtx); // printFirst() outputs "first". Do not change or remove this line.printFirst();this->isFirstDone = true;}cv.notify_all(); // 喚醒所有等待鎖的線程}void second(function<void()> printSecond) {// 立即加鎖,同lock_guard, 但是unique_lock比較靈活,還支持延遲加鎖。支持手動加鎖、釋放鎖。需要手動管理。std::unique_lock<std::mutex> lock(mtx);// 判斷是否滿足執行條件。如果不滿足就調用wait函數釋放鎖,該線程阻塞在這里等待被喚醒; 若滿足執行條件則繼續下面的代碼邏輯。 // 被喚醒會判斷是否滿足執行條件,且滿足條件則獲取鎖,然后繼續下面的代碼邏輯。不滿足條件則繼續等待。cv.wait(lock, [this](){return this->isFirstDone;}); // 這里的第二個參數是一個lambda表達式,表示一個匿名函數,該函數捕獲this指針(函數體中用到該指針),沒有參數。// printSecond() outputs "second". Do not change or remove this line.printSecond();this->isSecondDone = true;lock.unlock(); // 不要忘記釋放鎖。因為線程被喚醒后需要獲取鎖資源才會執行到這里,所以必須再釋放,不能因為上面wait函數釋放鎖了就不調用釋放了。cv.notify_all(); // 需要通知其他線程, 這里也可以調用notify_one, 只有一個線程在等待了。}void third(function<void()> printThird) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this]() {return this->isSecondDone;});// printThird() outputs "third". Do not change or remove this line.printThird();lock.unlock();}};Foo foo; // 主線程生成Foo對象。// 因為線程要執行對象里的成員函數,所以第一個參數是函數指針, 第二個參數是該對象,后面的參數是該成員函數的傳參,這里傳遞的函數std::thread t1(&Foo::first, &foo, printFirst);std::thread t3(&Foo::third, &foo, printThird);std::thread t2(&Foo::second, &foo, printSecond);t1.join();t2.join();t3.join();std::cout << endl << "finish" << endl;
}
執行結果:
ok!
提交到LeeCode:
ok! 沒問題。
反面教材:
void testLeeCode1114() { // LeeCode1114.按序打印. 反面教材,主線程加鎖,子線程解鎖,報錯:unlock of unowned mutex 。 class Foo {mutex mtx_1, mtx_2;unique_lock<mutex> lock_1, lock_2;public:Foo() : lock_1(mtx_1, try_to_lock), lock_2(mtx_2, try_to_lock) {}void first(function<void()> printFirst) {printFirst();lock_1.unlock();}void second(function<void()> printSecond) {lock_guard<mutex> guard(mtx_1);printSecond();lock_2.unlock();}void third(function<void()> printThird) {lock_guard<mutex> guard(mtx_2);printThird();}};Foo foo; // 主線程生成Foo對象。std::thread t1(&Foo::first, &foo, printFirst); // 因為是線程要執行對象里的成員函數,所以第一個參數是函數指針, 第二個參數是該對象,后面的參數是該成員函數的傳參,這里傳遞的函數std::thread t3(&Foo::third, &foo, printThird);std::thread t2(&Foo::second, &foo, printSecond);// 主線程等這3個線程執行結束:t1.join();t2.join();t3.join();// 報錯: unlock of unowned mutex
}
?報錯:unlock of unowned mutex 。?
問題就在于主線程加鎖, 然后子線程解鎖。所以報錯。線程必須先占有鎖資源才能解鎖。
總結: 互斥鎖就類比一個單人用的衛生間。一個人(線程)進去了會把衛生間鎖住(加鎖), 此時其他人(線程)想進去只能等待鎖釋放。
如果一個人進去衛生間后發現不滿足辦事條件,比如沒帶紙(如示例代碼中判斷不滿足執行條件),此時出去等待(如示例代碼的wait函數釋放鎖),別人進去衛生間完事后出來通知說他用完了,然后剛才出去等待的那個人再次競爭到衛生間進去了, 然后會再次檢查條件是否滿足(如代碼中的條件判斷),發現衛生間有紙了,ok可以方便了。