c++ - 并发模型 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);
其中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
调用 bar 时,然后在t = 10
调用 foo 时, foo 穿过由 mutex 保护的临界区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 论文 https://lamport.azurewebsites.net/pubs/time-clocks.pdf
解决方案
由于 fencing 属性,C++ 内存模型是否保证当 bar 函数从等待 cv2 恢复时 flag 的值将设置为 true?
就其本身而言,条件变量容易产生虚假唤醒。没有谓词子句的CV.wait(lck)
调用可能由于各种原因返回。这就是为什么在进入之前在 while 循环中检查谓词条件总是很重要的原因wait
。你永远不应该假设当wait(lck)
返回时你正在等待的事情已经发生了。但是使用您在等待中添加的子句:cv2.wait(lck,[&]{return flag;});
此检查会为您处理。所以是的,当wait(lck, predicate)
返回时,那 flag
将是真的。
我们可以只使用一个条件变量来解决这个问题吗?
绝对地。只需摆脱cv2
并让两个线程在第一个cv
.
有没有办法在不使用锁而只使用原子的情况下做到这一点。原子对锁有哪些性能改进?
当您可以在一个线程上轮询而不是等待时,原子是很棒的。想象一个 UI 线程想要向您显示汽车的当前速度。speed
它会在每次帧刷新时轮询变量。但是另一个线程,“引擎线程”正在atomic<int> speed
为轮胎的每次旋转设置该变量。这就是它的亮点——当你已经有了一个轮询循环,并且在 x86 上,原子操作主要是用 LOCK 操作码前缀实现的(例如并发由 CPU 正确完成)。
至于只针对锁和原子的实现......好吧,对我来说已经晚了。简单的解决方案,两个线程都只是睡眠并轮询一个原子整数,该整数随着每个线程的轮次而递增。每个线程只是等待 value 为“last+2”并每隔几毫秒轮询一次。效率不高,但会奏效。
对我来说,关于如何使用单个或一对互斥锁来做到这一点已经有点晚了。
如果我在关键区域内执行 cv.notify_one() 和对应的 cv2.notify_one() 会发生什么,是否有可能错过中断。
不,你很好。只要您的所有线程都持有锁并在进入wait
调用之前检查它们的谓词条件。您可以notify
在关键区域内部或外部进行调用。我总是建议做notify_all
over notify_one
,但这甚至可能是不必要的。
推荐阅读
- java - 我将如何填充单元格网格以在 Java 中进行寻路?
- maven - 如何将 fbinfer 与我的 Maven 项目集成?
- c# - 想将指纹图像保存在数据库中,并且还想检索相同的图像
- angular - 当我在做 ng serve -o 时,出现未处理的异常:ts.createNodeArray is not a function
- mongodb - 有没有办法找到给定数组的哪些元素不在数据库中
- maven-3 - 我收到错误消息:无法执行插件“Cucumber Artifact Copier”
- mysql - 在 MySQL 中使用 Json 键值对对象查找和替换字符串
- azure - Azure VM - 注册表更改阻止启动机器
- javascript - 如何模拟 AWS sqs 调用以进行单元测试
- arduino - IMU 设备 - 如何在设备垂直时通过计算欧拉角获得水平旋转角