【发布时间】:2015-10-16 13:40:54
【问题描述】:
我遇到了一个奇怪的 C++ 代码行为,不确定它是编译器错误还是只是我的代码的未定义/未指定行为。代码如下:
#include <unistd.h>
#include <iostream>
#include <thread>
struct Parent {
std::thread t;
static void entry(Parent* p) {
p->init();
p->fini();
}
virtual ~Parent() { t.join(); }
void start() { t = std::thread{entry, this}; }
virtual void init() { std::cout << "Parent::init()" << std::endl; }
virtual void fini() { std::cout << "Parent::fini()" << std::endl; }
};
struct Child : public Parent {
virtual void init() override { std::cout << "Child::init()" << std::endl; }
virtual void fini() override { std::cout << "Child::fini()" << std::endl; }
};
int main() {
Child c;
c.start();
sleep(1); // <========== here is it
return 0;
}
代码的输出如下,这并不奇怪:
Child::init()
Child::fini()
但是,如果函数调用“sleep(1)”被注释掉,输出将是:
Parent::init()
Parent::~fini()
在 Ubuntu 15.04 上测试,gcc-4.9.2 和 clang-3.6.0 显示相同的行为。编译器选项:
g++/clang++ test.cpp -std=c++11 -pthread
它看起来像一个竞争条件(在线程开始之前 vtable 没有完全构建)。这段代码格式不正确吗?编译器错误?还是应该是这样的?
【问题讨论】:
-
想一想:线程使用了子对象,但是子对象被销毁在线程加入之前(因为加入只发生在子对象销毁之后开始)!
-
加上 sleep 注释,你在析构函数中“调用”虚方法...
-
一个快速的解决方法是为 Child 创建一个析构函数并从那里调用
t.join();。你必须保护你的线程,但我再次说这是一个快速修复。 -
@KerrekSB 已经给了你正确的答案。顺便说一句,在您的计算机中您看到孩子因睡眠而起作用的事实具有误导性;其他计算机仍然可以看到调用的
Parent函数或Child然后Parent。 -
有些人在遇到问题时会想,“我知道,我会使用线程”,然后他们就会遇到两个问题。
标签: c++ multithreading thread-safety race-condition