我有一个cpp文件(“ Plugin.cpp”),其中包含一个小插件,该插件最终被链接到一个更大的c ++ 11服务器项目中,作为共享库。
它需要启动一个线程并且出于惰性(这是一个快速的原型),我只是在Plugin.cpp中这样声明并启动了它:
#include <thread>
bool threadDone = false;
void myFunction() {
while(!threadDone) {
}
// do stuff
}
std::thread myThread = std::thread(&myFunction);
// Called by server to stop tell plugin to cleanup.
void stopPlugin() {
threadDone = true;
myThreadDone.join();
// ....
}
启动线程似乎完全是反模式,就像cpp文件是脚本文件一样,但是它可以工作,并且在较大的服务器项目中运行此代码时似乎也不会引起任何问题。
不涉及较大项目的详细信息:
有人可以详细说明这样做会带来哪些不良的意想不到的后果,而不是从对象或函数内部以更受控制的方式启动线程吗?
我很抱歉,如果这太明显了,我没有在网上找到这个答案,而且我最近还没有用c ++编写代码,所以我很生疏。但是在我看来,某些事情最终应该会出错。
答案 0 :(得分:3)
我会使用std::atomic<bool>
而不是bool threadDone
。有关更多详细信息,请参见When do I really need to use atomic<bool> instead of bool?。
在声明std::thread
对象的方式上,您无法直接控制线程将在何时开始。是否迫切需要以这种方式声明线程对象?另一个重要的考虑因素是,如果您的共享库是针对与目标服务器不同的目标加载/链接的,则它是否会产生其他意外线程?
如果您有一个stopPlugin
方法,服务器可以调用该方法来停止线程,则始终可以编写一个startPlugin
方法,并将实际的线程对象很好地包装在下面的简单接口中。
class Plugin {
public:
void startPlugin();
void stopPlugin();
private:
std::thread myThread;
};
即使对于更简单的结构,我也很少像使用静态存储那样使用静态存储,并且倾向于使用construct on first use惯用语。
答案 1 :(得分:1)
您的示例有几个问题。
threadDone
是具有数据竞争的共享数据 threadDone
是共享数据(从主线程写入,从插件线程读取)。在您的示例中,没有同步机制(例如std::mutex
或std::atomic
)。因此,您的示例中发生了数据竞赛;产生未定义的行为。
使用C ++处理线程时,您应该了解what the C++ memory model says about threads。
通过以下代码的inspecting the assembly,您可以轻松地验证该代码不是线程安全的:
namespace buggy {
bool threadDone = false;
unsigned turn = 0u;
void myFunction() {
while(!threadDone) {
++turn;
}
}
}// buggy
显示为
buggy::myFunction():
cmp BYTE PTR buggy::threadDone[rip], 0
jne .L1
.L3:
jmp .L3
.L1:
ret
请注意,threadDone
仅被比较一次。如果为假,则存在无限循环(无增量)。
解决上述问题的最简单方法是使用原子:
#include <atomic>
namespace fixed {
std::atomic<bool> threadDone{false};
std::atomic<unsigned> turn{0u};
void myFunction() {
while(!threadDone.load()) {
turn.fetch_add(1u);
}
}
}// fixed
产生装配体
fixed::myFunction():
movzx eax, BYTE PTR fixed::threadDone[rip]
test al, al
jne .L5
.L8:
lock add DWORD PTR fixed::turn[rip], 1
movzx eax, BYTE PTR fixed::threadDone[rip]
test al, al
je .L8
.L5:
ret
我希望插件中的线程将全局数据用作输入和输出。难以确保在使用之前正确初始化数据。
startPlugin
和stopPlugin
可能会失败这只是说明接口不对称(startPlugin
不可用,因为插件始终在---何时启动?)。另外,如果线程不是stopPlugin
(例如,如果joinable
被两次调用),则stopPlugin
失败。
后者可能不是一个真正的问题。