没有像mutex_Lock这样的协调机制的线程
C++ - Threads without coordinating mechanism like mutex_Lock
两天前我参加了一个面试。被面试的人c++很好,但多线程不行。当他让我写一个多线程代码的两个线程,其中一个线程打印1,3,5,…还有其他的指纹2 4 6…但是,输出应该是1,2,3,4,5 ....因此,我给出了下面的代码(sudo code)
mutex_Lock LOCK;
int last=2;
int last_Value = 0;
void function_Thread_1()
{
while(1)
{
mutex_Lock(&LOCK);
if(last == 2)
{
cout << ++last_Value << endl;
last = 1;
}
mutex_Unlock(&LOCK);
}
}
void function_Thread_2()
{
while(1)
{
mutex_Lock(&LOCK);
if(last == 1)
{
cout << ++last_Value << endl;
last = 2;
}
mutex_Unlock(&LOCK);
}
}
在这之后,他说"这些线程即使没有那些锁也能正常工作。"这些锁会降低效率。"我的观点是,如果没有锁,就会出现这样的情况:一个线程会检查(last == 1或2),同时另一个线程会尝试将值更改为2或1。所以,我的结论是,它将工作没有锁,但这不是一个正确的/标准的方式。现在,我想知道谁是正确的,在什么基础上? 如果没有锁,并发运行两个函数将是未定义的行为,因为在访问last
和last_Value
时存在数据竞争,而且(尽管不会导致UB)打印将是不可预测的。
我想面试官可能考虑过使用原子变量。
[源]std::atomic模板的每个实例化和完全特化都定义了一个原子类型。原子类型的对象是c++中唯一免于数据竞争的对象;也就是说,如果一个线程写入原子对象,而另一个线程从中读取,则行为是定义良好的。此外,对原子对象的访问可以建立线程间同步,并按照std::memory_order指定的顺序对非原子内存进行访问。
我的意思是你唯一应该改变的是删除锁并将last
变量更改为std::atomic<int> last = 2;
而不是int last = 2;
这样可以安全地并发访问last
变量。
出于好奇,我编辑了一下你的代码,并在我的Windows机器上运行:
#include <iostream>
#include <atomic>
#include <thread>
#include <Windows.h>
std::atomic<int> last=2;
std::atomic<int> last_Value = 0;
std::atomic<bool> running = true;
void function_Thread_1()
{
while(running)
{
if(last == 2)
{
last_Value = last_Value + 1;
std::cout << last_Value << std::endl;
last = 1;
}
}
}
void function_Thread_2()
{
while(running)
{
if(last == 1)
{
last_Value = last_Value + 1;
std::cout << last_Value << std::endl;
last = 2;
}
}
}
int main()
{
std::thread a(function_Thread_1);
std::thread b(function_Thread_2);
while(last_Value != 6){}//we want to print 1 to 6
running = false;//inform threads we are about to stop
a.join();
b.join();//join
while(!GetAsyncKeyState('Q')){}//wait for 'Q' press
return 0;
}
,输出总是:
1
2
3
4
5
6
Ideone拒绝运行此代码(编译错误)..
编辑:但这是一个工作的linux版本:)(感谢很快)
面试官不知道他在说什么。没有锁,last
和last_value
上都有竞争。例如,编译器可能会在last_value
的打印和递增之前重新排序对last
的赋值,这可能会导致另一个线程执行过时的数据。此外,您可能会得到交错输出,这意味着两个数字不会被换行分隔。
另一件可能出错的事情是,编译器可能决定不重新加载last
和(不太重要的)last_value
,因为它不能(安全地)在这些迭代之间改变(因为数据竞争在c++ 11标准中是非法的,并且在以前的标准中不承认)。这意味着面试官建议的代码实际上很有可能产生无限循环,导致什么都不做。
虽然可以在没有互斥的情况下使代码正确,但这绝对需要具有适当排序约束的原子操作(在if语句中加载last
时对last
和acquire
赋值的释放语义)。
当然,由于有效地序列化了整个执行过程,您的解决方案确实降低了效率。然而,由于运行时几乎完全花费在流输出操作中,这几乎肯定是通过使用锁进行内部同步的,因此您的解决方案不会再降低效率了。在代码中等待锁实际上可能比忙碌地等待它更快,这取决于可用资源(使用原子的非锁定版本在单核机器上执行时绝对会崩溃)
- 使用共享指针时,从共享指针本身释放内存的机制是什么
- 为什么 boost::comb 对结构化绑定的支持缺少结构化绑定机制对 boost::tuples::cons 的适应?
- 了解使用堆栈实现队列的递归调用机制
- 更好的事件处理机制?
- C++活动异常机制背后的推理
- 在信号槽机制与传统环路之间做出决定
- 虚拟继承的内部机制
- 任何类型的缓存机制
- 在两个类上协调析构函数,其中一个类需要首先清理
- C++20 中协程的机制是什么
- C++ 中的纯虚函数机制如何公开来自 DLL 的函数
- 派生类中函数参数变化的虚函数按常量类型在"function parameter"会破坏虚拟机制吗?
- 假想的锁定机制:非阻滞写,阅读和无效
- 将 boost::时序机制包装成类编译错误?
- 来自文本字符串或某种其他机制的代码类自动生成器
- 如何在 OpenCL 中使用缓冲区分配和映射内存机制
- 屏幕上协调对象
- 为什么std :: map中有一个分类机制
- 是我的等待 - 使用std :: mutex通知机制正确
- 没有像mutex_Lock这样的协调机制的线程