为练习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可以方便了。
评论前必须登录!
注册