这段代码是安全的,可以从构造函数C ++生成一个线程吗?

时间:2012-06-14 11:27:03

标签: c++ multithreading c++11 stdthread

我需要在C ++类中嵌入一个线程,一种活动对象,但不完全是。我正在从类的构造函数中生成线程,这样做是否可以使用此方法存在任何问题。

#include <iostream>
#include <thread>
#include <unistd.h>

class xfer
{
        int i;
        std::shared_ptr<std::thread> thr;
        struct runnable {
                friend class xfer;
                void operator()(xfer *x) {
                        std::cerr<<"thread started, xfer.i:"<<x->i;
                }
        } run;
        public:
        xfer() try : i(100), thr(new std::thread(std::bind(run, this))) { } catch(...) { }
        ~xfer() { thr->join(); }
};

int
main(int ac, char **av)
{
        xfer x1;
        return 0;
}

3 个答案:

答案 0 :(得分:3)

通常,在构造函数中启动线程没有问题, 提供线程中使用的对象已经完全构造 在你开始之前。 (因此,例如,有一个线程的习语 基类,它在构造函数中启动自身的线程,是 在你的情况下,你没有遇到这个标准,因为 thread对象使用你的成员run,直到之后才构造它 在你开始线程之后。将线程的创建移动到 构造函数的主体,或者只是改变数据的顺序 类定义中的成员将更正此问题。 (如果你这样做的话 后者,添加注释,说明订单很重要, 为什么。)

在析构函数中调用join更成问题。任何操作 可能会等待不确定的时间是,恕我直言,有问题 析构函数。在堆栈展开期间调用析构函数时,您 不想坐在那里等待其他线程完成。

此外,您可能希望使该类不可复制。 (在这种情况下, 你不需要shared_ptr。)如果你复制,你最终会做的 join两次在同一个帖子上。

答案 1 :(得分:2)

您的代码有竞争条件,并且不安全。

问题是成员变量的初始化按它们在类中声明的顺序发生,这意味着成员thr在成员run之前初始化。在初始化thr期间,您正在std::bind上调用run,这将在内部复制(尚未初始化的)run对象(您知道您在那里复制吗?)

假设您传递了指向run(或使用std::refstd::bind以避免副本)的指针,代码仍然会有竞争条件。将指针/引用传递给std::bind在这种情况下不会成为问题,因为将指针/引用传递给尚未初始化的成员,只要它不被访问就可以了。 。 但是,仍然存在竞争条件,因为std::thread对象可能产生过快的线程(比如运行构造函数的线程被驱逐,新线程处理),并且在{/ 1}}对象初始化之前,最终可以执行run.operator() 执行的线程。

您需要重新排序类型中的字段以使代码安全。现在您知道成员顺序的微小变化会对代码的有效性产生令人不安的影响,您可能还会考虑更安全的设计。 (此时它是正确的,它可能实际上是你需要的,但至少注释类定义,以便以后没有人重新排序字段)

答案 2 :(得分:0)

在构造函数中的使用可能是安全的,但是......

  • 您应避免在函数调用中使用 bare new
  • 在析构函数中使用thr->join()是......表明存在问题

详情......

请勿在函数调用中使用裸new

由于未指定操作数的执行顺序,因此它不是异常安全的。更喜欢在这里使用std::make_shared,或者在unique_ptr的情况下,创建自己的make_unique工具(具有完美的转发和可变参数模板,它只是有效);这些构建器方法将确保RAII类的所有权分配和归属不可分割地执行。

了解三条规则

三规则是一个经验法则(为C ++ 03建立),它表示如果你需要编写一个复制构造函数,赋值运算符或析构函数,你应该写另外两个。

在您的情况下,生成的赋值运算符不正确。也就是说,如果我创建了两个对象:

 int main() {
     xfer first;  // launches T1
     xfer second; // launches T2

     first = second;
 }

然后在执行作业时我丢失T1的引用,但我从未加入过它!

我无法回想join电话是否是强制性的,但是你的课程只是不一致。如果不是强制性的,请删除析构函数;如果它是强制性的,那么你应该编写一个只处理一个共享线程的低级类,然后确保共享线程的销毁总是在join调用之前,最后使用该共享线程设施直接在你的xfer班。

根据经验,一个对其元素子集有特殊复制/分配需求的类应该被拆分,以隔离一个(或几个)专用类中具有特殊需求的元素。