【问题标题】:Concurrency Model C++并发模型 C++
【发布时间】:2020-03-27 06:42:05
【问题描述】:

假设给你以下代码:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

FooBar 的同一个实例将被传递给两个不同的线程。线程 A 将调用 foo(),而线程 B 将调用 bar()。修改给定程序以输出“foobar” n 次。

对于leetcode下面的问题我们要写两个函数

void foo(function<void()> printFoo);
void bar(function<void()> printBar);

其中printFoo 和相应的printBar 是一个打印Foo 的函数指针。函数foobar 在多线程环境中被调用,并且没有关于如何调用foobar 的顺序保证。

我的解决方案是

class FooBar {
private:
    int n;
    mutex m1;
    condition_variable cv;
    condition_variable cv2;
    bool flag;
public:
    FooBar(int n) {
        this->n = n;
        flag=false;
    }

    void foo(function<void()> printFoo) {

        for (int i = 0; i < n; i++) {
            unique_lock<mutex> lck(m1);
            cv.wait(lck,[&]{return !flag;});
            printFoo();
            flag=true;
            lck.unlock();
            cv2.notify_one();
        }
    }

    void bar(function<void()> printBar) {

        for (int i = 0; i < n; i++) {

            unique_lock<mutex> lck(m1);
            cv2.wait(lck,[&]{return flag;});
            printBar();
            flag=false;
            lck.unlock();
            cv.notify_one();

            // printBar() outputs "bar". Do not change or remove this line.

        }
    }
};

让我们假设,在时间 t = 0 bar 被调用,然后在时间 t = 10 foo 被调用,foo 穿过由互斥锁 m1 保护的临界区。

我的问题是

由于防护属性,C++ 内存模型是否保证当bar 函数从等待cv2 恢复时,flag 的值将设置为 true?

我是否正确假设线程之间共享的锁强制执行之前和之后的关系,如 Leslie Lamports 时钟系统的方式所示。编译器和 C++ 保证临界区(这里是锁的结束)结束之前的所有内容都将被任何租用锁的线程观察到,因此可以将常见的锁、原子、信号量可视化为前后执行通过在多线程环境中建立时间来行为。

我们可以只使用一个条件变量来解决这个问题吗?

有没有办法在不使用锁而只使用原子的情况下做到这一点。原子对锁有哪些性能改进?

如果我在关键区域内执行cv.notify_one() 和相应的cv2.notify_one() 会发生什么情况,是否有可能错过中断。

原来的问题 https://leetcode.com/problems/print-foobar-alternately/

Leslie Lamports 论文 https://lamport.azurewebsites.net/pubs/time-clocks.pdf

【问题讨论】:

    标签: c++ multithreading


    【解决方案1】:

    由于 fencing 属性,C++ 内存模型是否保证当 bar 函数从等待 cv2 恢复时,flag 的值将设置为 true?

    就其本身而言,条件变量容易产生虚假唤醒。没有谓词子句的CV.wait(lck) 调用可能会由于各种原因返回。这就是为什么在输入wait 之前在while 循环中检查谓词条件总是很重要的原因。你永远不应该假设当wait(lck) 返回时,你等待的事情已经发生了。但是使用您在等待中添加的子句:cv2.wait(lck,[&amp;]{return flag;}); 此检查会为您处理。所以是的,当wait(lck, predicate) 返回时,flag 将为真。

    我们可以只使用一个条件变量来解决这个问题吗?

    当然。只需摆脱 cv2 并让两个线程在第一个 cv 上等待(并通知)。

    有没有办法在不使用锁而只使用原子的情况下做到这一点。原子对锁有哪些性能改进?

    当您可以在一个线程上轮询而不是等待时,原子是很棒的。想象一个 UI 线程想要向您显示汽车的当前速度。它会在每次帧刷新时轮询speed 变量。但是另一个线程,“引擎线程”正在设置atomic&lt;int&gt; speed 变量,每次轮胎旋转。这就是它的亮点——当你已经有一个轮询循环时,并且在 x86 上,原子主要是使用 LOCK 操作码前缀实现的(例如,并发由 CPU 正确完成)。

    至于只针对锁和原子的实现......好吧,对我来说已经晚了。简单的解决方案,两个线程都只是睡眠并轮询一个原子整数,该整数随着每个线程的轮次而递增。每个线程只是等待 value 为“last+2”并每隔几毫秒轮询一次。效率不高,但会起作用。

    对于如何使用单个或一对互斥锁来做这件事,我在晚上有点晚了。

    如果我在临界区内执行 cv.notify_one() 和相应的 cv2.notify_one() 会发生什么情况,是否有可能错过中断。

    不,你很好。只要您的所有线程在进入wait 调用之前都持有锁并检查它们的谓词条件。您可以在关键区域内部或外部调用notify。我总是建议使用notify_all 而不是notify_one,但这甚至可能是不必要的。

    【讨论】:

    • 一些额外的问题
    • @Varun - 我不知道你的额外问题是什么。无论如何,Stack Overflow 并不是一个真正的讨论频道。它更好地服务于可以将他们的编程问题总结为一个问题的用户。所以我真的不能再花时间在这个问题上了——特别是如果你在我把你带到 1 码线之后移动球门柱。在某些时候,你只需要自己做功课。
    • stackoverflow 不允许我编辑我的问题,我只是想澄清一下,假设没有虚假唤醒,由于内存模型的围栏属性,另一个线程看到了正确的标志值?
    • 只要您在锁定状态下访问变量,就始终可以保证看到变量的正确版本。当您访问多核机器上的锁之外的数据并且可能在几个周期内看不到最新更新时,有一个称为“缓存一致性问题”的概念。锁有助于使缓存失效,这样就不会发生这种情况。
    猜你喜欢
    • 1970-01-01
    • 2013-05-21
    • 2010-09-18
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-12
    • 2019-10-14
    相关资源
    最近更新 更多