并发模型C++



假设您得到以下代码:

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的函数指针。函数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 = 0bar和调用t = 10foo时,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,但这可能是不必要的。

最新更新