1114. 按序打印

我们提供了一个类:

public class Foo { public void first() { print(“first”); } public void second() { print(“second”); } public void third() { print(“third”); }

}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

一个将会调用 first() 方法

一个将会调用 second() 方法

还有一个将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

互斥锁

互斥锁是用来防止多个线程同时访问共享资源对象的机制,在同一时间只有一个线程可以拥有一个特定的锁对象,其他线程如果尝试获取锁会阻塞直到锁资源被释放或直接返回失败。

针对这道题我们可以用两个互斥锁来阻塞 second 和 third 函数,分别在 first 和 second 执行结束后解锁。

  1. mutex mtx1,mtx2;
  2. void first(function<void()> printFirst) {
  3. printFirst();
  4. mtx1.unlock();
  5. }
  6. void second(function<void()> printSecond) {
  7. mtx1.lock();
  8. printSecond();
  9. mtx1.unlock();
  10. mtx2.unlock();
  11. }
  12. void third(function<void()> printThird) {
  13. mtx2.lock();
  14. printThird();
  15. mtx2.unlock();
  16. }

注意:上述代码本质上是错误的!如下几点:

  • mutex在加锁解锁过程中,其所有权必须在同一线程上!

    由同一个线程来对一个 mutex 对象进行 lockunlock 操作

RAII机制优化

因为 mutex 对象本身是不保护任何数据的,我们只是通过 mutex 的机制来保护数据被同时访问,所以最好使用 lock_guard 或者 unique_lock 提供的 RAII 机制来管理 mutex 对象,而不是直接操作 mutex 对象;其中 lock_guard 只拥有构造和析构函数,用来实现 RAII 机制,而 unique_lock 是一个完整的 mutex 所有权包装器,封装了所有 mutex 的函数:

条件变量

std::condition_variable 是一种用来同时阻塞多个线程的同步原语(synchronization primitive),std::condition_variable必须和 std::unique_lock搭配使用:

  1. class Foo {
  2. condition_variable cv;
  3. mutex mtx;
  4. int k = 0;
  5. public:
  6. void first(function<void()> printFirst) {
  7. printFirst();
  8. k = 1;
  9. cv.notify_all(); // 通知其他所有在等待唤醒队列中的线程
  10. }
  11. void second(function<void()> printSecond) {
  12. unique_lock<mutex> lock(mtx); // lock mtx
  13. cv.wait(lock, [this](){ return k == 1; }); // unlock mtx,并阻塞等待唤醒通知,需要满足 k == 1 才能继续运行
  14. printSecond();
  15. k = 2;
  16. cv.notify_one(); // 随机通知一个(unspecified)在等待唤醒队列中的线程
  17. }
  18. void third(function<void()> printThird) {
  19. unique_lock<mutex> lock(mtx); // lock mtx
  20. cv.wait(lock, [this](){ return k == 2; }); // unlock mtx,并阻塞等待唤醒通知,需要满足 k == 2 才能继续运行
  21. printThird();
  22. }
  23. };

异步操作

image.png

  1. class Foo {
  2. promise<void> pro1, pro2;
  3. public:
  4. void first(function<void()> printFirst) {
  5. printFirst();
  6. pro1.set_value();
  7. }
  8. void second(function<void()> printSecond) {
  9. pro1.get_future().wait();
  10. printSecond();
  11. pro2.set_value();
  12. }
  13. void third(function<void()> printThird) {
  14. pro2.get_future().wait();
  15. printThird();
  16. }
  17. };

image.png

  1. class Foo {
  2. function<void()> task = []() {};
  3. packaged_task<void()> pt_1{ task }, pt_2{ task };
  4. public:
  5. void first(function<void()> printFirst) {
  6. printFirst();
  7. pt_1();
  8. }
  9. void second(function<void()> printSecond) {
  10. pt_1.get_future().wait();
  11. printSecond();
  12. pt_2();
  13. }
  14. void third(function<void()> printThird) {
  15. pt_2.get_future().wait();
  16. printThird();
  17. }
  18. };

原子操作

  1. class Foo {
  2. std::atomic<bool> a{ false };
  3. std::atomic<bool> b{ false };
  4. public:
  5. void first(function<void()> printFirst) {
  6. printFirst();
  7. a = true;
  8. }
  9. void second(function<void()> printSecond) {
  10. while (!a)
  11. this_thread::sleep_for(chrono::milliseconds(1));
  12. printSecond();
  13. b = true;
  14. }
  15. void third(function<void()> printThird) {
  16. while (!b)
  17. this_thread::sleep_for(chrono::milliseconds(1));
  18. printThird();
  19. }
  20. };

image.png