在main外部声明并启动c ++ 11线程

时间:2018-08-28 19:50:23

标签: c++11 c++14

我有一个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 ++编写代码,所以我很生疏。但是在我看来,某些事情最终应该会出错。

2 个答案:

答案 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::mutexstd::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

初始化顺序

我希望插件中的线程将全局数据用作输入和输出。难以确保在使用之前正确初始化数据。

缺少startPluginstopPlugin可能会失败

这只是说明接口不对称(startPlugin不可用,因为插件始终在---何时启动?)。另外,如果线程不是stopPlugin(例如,如果joinable被两次调用),则stopPlugin失败。

后者可能不是一个真正的问题。