0

我需要在 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;
}
4

3 回答 3

4

一般来说,在构造函数中启动线程是没有问题的, 前提是线程中使用的对象在启动之前已经完全构造。(因此,例如,有一个线程基类的习惯用法,它在其构造函数中启动线程本身,就被打破了。)在你的情况下,你还没有遇到这个标准,因为线程对象使用你的 member run,它直到您启动线程之后才构造。将线程的创建移到构造函数的主体中,或者简单地更改类定义中数据成员的顺序将更正此问题。(如果您选择后者,添加注释,说明顺序很重要,以及为什么。)

Calling join in the destructor is more problematic. Any operation which may wait an indeterminate amount of time is, IMHO, problematic in a destructor. When a destructor is called during stack unwinding, you don't want to sit around waiting for the other thread to finish.

Also, you probably want to make the class uncopyable. (In which case, you don't need shared_ptr.) If you copy, you'll end up doing the join twice on the same thread.

于 2012-06-14T12:24:20.380 回答
2

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

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

假设您传递了一个指针run(或用于std::ref避免std::bind复制),代码仍然会有竞争条件。在这种情况下,将指针/引用传递给std::bind将不是问题,因为可以将指针/引用传递给尚未初始化的成员,只要它未被访问但是,仍然存在竞争条件,即std::thread对象可能会太快地生成线程(例如,运行构造函数的线程被逐出,并且新线程处理),并且最终可能导致线程在对象被执行run.operator() 之前run执行初始化。

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

于 2012-06-14T12:14:53.483 回答
0

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

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

详细...

不要new在函数调用中使用bare

由于未指定操作数的执行顺序,因此它不是异常安全的。更喜欢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班级。

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

于 2012-06-14T11:53:11.857 回答