首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >并发模型C++

并发模型C++
EN

Stack Overflow用户
提问于 2020-03-27 14:42:06
回答 1查看 160关注 0票数 0

假设您得到了以下代码:

代码语言:javascript
复制
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的问题,我们必须编写两个函数

代码语言:javascript
复制
void foo(function<void()> printFoo);
void bar(function<void()> printBar);

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

我的解决方案是

代码语言:javascript
复制
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被调用的时候,然后在互斥锁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 Paper https://lamport.azurewebsites.net/pubs/time-clocks.pdf

EN

回答 1

Stack Overflow用户

发布于 2020-03-27 15:05:25

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

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

我们能用一个条件变量解决这个问题吗?

绝对一点儿没错。只需去掉cv2,让两个线程在第一个cv上等待(并通知)。

有一种方法可以在不使用锁和原子的情况下做到这一点。与锁相比,原子提供了哪些性能改进?

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

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

对于我来说,现在讨论如何使用一个或两个互斥锁来做这件事有点太晚了。

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

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60881104

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档