假设您得到了以下代码:
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的函数指针。函数foo和bar在多线程环境中被调用,并且没有关于如何调用foo和bar的顺序保证。
我的解决方案是
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
发布于 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,但这可能是不必要的。
https://stackoverflow.com/questions/60881104
复制相似问题