假设您得到以下代码:
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);
其中CCD_ 4和相应的CCD_ 5是打印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
bar和调用t = 10
foo时,foo通过由互斥对象m1
保护的关键部分。
我的问题是
由于fencing属性,C++内存模型是否保证当bar
函数从cv2
上的等待恢复时,flag的值将设置为true?
假设线程之间共享的锁强制执行前后关系,如Leslie Lamports时钟系统所示,我是对的吗。编译器和C++保证任何租用锁的线程都会观察到关键部分结束之前的一切(这里是锁的结束(,因此通过在多线程环境中建立时间,可以将常见的锁、原子、信号量可视化为前后行为。
我们能只用一个条件变量来解决这个问题吗?
有没有一种方法可以做到这一点,而不使用锁,只使用原子。原子论对锁有哪些性能改进?
如果我在临界区域内执行cv.notify_one()
和相应的cv2.notify_one()
,会发生什么,是否有可能错过中断。
原始问题https://leetcode.com/problems/print-foobar-alternately/.
Leslie Lamports Paperhttps://lamport.azurewebsites.net/pubs/time-clocks.pdf
C++内存模型是否因为fencing属性而保证当bar函数从等待cv2恢复时,flag的值将设置为true?
就其本身而言,条件变量容易出现虚假唤醒。不带谓词子句的CV.wait(lck)
调用可能由于各种原因而返回。这就是为什么在输入wait
之前检查while循环中的谓词条件总是很重要的。您永远不应该认为当wait(lck)
返回时,您正在等待的事情实际上已经发生了。但有了您在等待中添加的条款: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
,但这可能是不必要的。