【问题标题】:Call a method with thread in C++在 C++ 中使用线程调用方法
【发布时间】:2017-10-29 19:27:48
【问题描述】:

如何用线程调用类方法?

我的班级:

using namespace std;
class series{
private:
    string filename;
    vector<double> data;
public:
    series(string _filename);
    int loaddata(const int col);
    void readdata() const;

};

series::series(string _filename):filename(_filename) {}

int series::loaddata(const int col)
{
    ifstream input(filename);
    string line;
    if (!input) {
        cout << "File failed to open" << endl;
        return 0;
    }
    while(!input.eof())
    {
        while(getline(input, line)){
            vector<string> oneline;
            boost::split(oneline, line, boost::is_any_of("|"));
            data.push_back(boost::lexical_cast<double>(oneline[col]));
        }

    }
    return 1;
}

从 main 调用它,只发布相关部分。 csv 是文件名向量,基本上是字符串向量。

vector<series> ts;
int col = 0;
vector<thread> th;
for (unsigned int i = 0; i != csv.size(); ++i) {
    ts.push_back(series(csv[i]));
    th.emplace_back(thread(&series::loaddata, ref(ts[i]), col));
}

给出我无法理解的错误。

/usr/include/c++/4.8/functional: In instantiation of ‘struct std::_Bind_simple<std::_Mem_fn<int (series::*)(int)>(std::reference_wrapper<series>, int)>’:
/usr/include/c++/4.8/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = int (series::*)(int); _Args = {std::reference_wrapper<series>, int}]’
/home/d066537/ClionProjects/correlation/src/main.cpp:105:66:   required from here
/usr/include/c++/4.8/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<int (series::*)(int)>(std::reference_wrapper<series>, int)>’
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/4.8/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<int (series::*)(int)>(std::reference_wrapper<series>, int)>’
         _M_invoke(_Index_tuple<_Indices...>)

请解决,对程序使用 Clion CMake,是的,线程适用于免费功能,所以我认为这与任何编译器标志无关。

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lpthread -std=c++11")

如果我从对象中删除 ref 包装器,我会收到此错误:

terminate called after throwing an instance of 'std::system_error' what(): Enable multithreading to use std::thread: Operation not permitted

现在更新到 g++-5,没有 ref 包装器,再次出现错误: CMakeFiles/correlation.dir/src/main.cpp.o:在函数中

`std::thread::thread<int (series::*)(int), series&, int>(int (series::*&&)(int), series&, int&&)':
/usr/include/c++/5/thread:137: undefined reference to `pthread_create'

【问题讨论】:

  • 您不需要为要用于线程函数的对象使用ref 包装器。
  • 另请注意,GCC 4.8 版现在已经很老了,它没有完整的 C++11 功能。尝试更新到更高版本的 GCC,然后重试。
  • 如果我删除 ref wrapper 我得到这个错误:在抛出一个 'std::system_error' 实例后调用终止 what(): Enable multithreading to use std::thread: Operation not allowed
  • 最后(但与您的问题无关,这很可能是由于您的 GCC 的旧且未完全 c++11 状态),请花点时间阅读Why is iostream::eof inside a loop condition considered wrong?
  • 查找there

标签: c++ multithreading class-method


【解决方案1】:

您将指针创建为临时的、堆栈分配的变量,并将 emplace_back 它添加到您的向量中。当你退出for循环迭代时,描述符被执行,std::terminate将被调用以终止正在运行的线程。

解决此问题的一种方法是改用std::vector&lt;std::thread*&gt;

vector<thread*> th;
for (unsigned int i = 0; i != csv.size(); ++i) {
    ts.push_back(series(csv[i]));
    thread t = new thread(&series::loaddata, ref(ts[i]), col)
    th.emplace_back(t);
    th[i]->join();
}

完成加载数据后记得delete你的线程。

【讨论】:

  • 不行,我试过thread *t = new thread(&series::loaddata, ref(ts[i]), col)
  • 忘了补充你需要在每个线程上使用join()(修改了我的答案)。
  • 是的,我可以使用std::asyncstd::future 给出答案,但我想解释一下这种方法有什么问题,并提供一个简单的修复,OP 可以很容易理解。
  • 不适用于 ref,如果我删除,它可以工作,但是当从线程调用 loaddata 时 ts 不会加载数据:(
【解决方案2】:

这是解决我在您的代码中发现的直接问题的一种方法。我还在各处添加了std::,因为using namespace std is bad

std::vector<series> ts;
ts.reserve(csv.size());  // [1]

int col = 0;
std::vector<std::thread> th;
th.reserve(csv.size());  // [1a] optional

for (unsigned int i = 0; i != csv.size(); ++i) {
    ts.push_back(series(csv[i]));
    th.emplace_back(&series::loaddata, &ts[i], col);  // [2]
}

// Lots of other code could be executed here. Joining only has to happen 
// at *some* point after creating the threads.

// [3]
for (auto& thread : th) {
    thread.join();
}

[2]

让我们从这里开始,因为这是最直接的问题。用 std::thread 调用成员函数的模式是:

std::thread(ptr-to-member-func, ptr-to-object, member-func-args...);

本质上,您必须提供在执行loaddata() 时将变为this 的对象。它被称为“this 指针”是有原因的。 ;)

另外,emplace_back() 是一个转发函数,它只接受要放入向量中的对象的构造函数参数。在这里显式构造 std::thread(...) 会破坏目的。

[3]

这个也很简单。您必须确保线程可以完成所有工作并正确退出。这意味着在某个时候调用join(),这将阻塞直到线程完成。如果您想退出整个程序时仍然有线程在运行,这一点尤其重要。

请注意,像 ΔλЛ 的答案那样在 for 循环中调用 join() 是行不通的。如果你这样做,你将创建一个线程,等待它完成,然后继续下一个线程。这是一种复杂的单线程执行方式。

[1]

关于向量(以及基本上所有其他标准容器)如何工作的小提示。它分配一块内存并将其用于push_back() 的项目。当它用完空间时,它会分配一个新的更大的内存块,复制或移动那里的现有项目并继续push_back()。重新分配意味着对项的所有引用和指针以及向量中的所有迭代器都将失效。

这让我们回到 [2]。在那里,您将指针传递给向量ts 中的一个项目。但是ts 必须在循环期间的某个时刻重新分配(至少这是你必须假设的)——让线程带有悬空指针。这是典型的未定义行为。

有多种方法可以解决这个问题。 [1] 显示了一个非常简单的方法,如果您事先知道向量将变得多大,它会很好地工作。 reserve() 为给定数量的元素分配足够的空间,从而防止重新分配。您的指针保持有效。

另一种解决方案是引入额外的间接:通常通过在堆上分配项目并将指针存储在向量中。像这样:

std::vector<std::unique_ptr<series>> ts;

for (...) {
    ts.push_back(new series(csv[i]));
    // In C++14 you would use std::make_unique instead of a raw new.

    th.emplace_back(
        &series::loaddata, 
        ts[i].get(),  // notice the difference
        col);
}

现在向量可以根据需要重新分配。它只需要复制一些指向它的新内存块的指针。实际series 对象的地址不再受影响。

[1a]

为什么我说这是可选的? th 将重新分配与 ts 相同的内容。但是,您不会保留任何指向线程对象的 ref 或指针。他们是否无效并不重要。并且std::thread 是可移动的,即它可以很好地重新分配。另一方面,内存分配是一项潜在的昂贵操作,在这种情况下避免它是微不足道的。

附言

查看std::async 以获得更高级的并发方法。线程是一个非常低级的结构。如果可以,最好避免直接与他们打交道。

【讨论】:

  • 顺便说一句,我很喜欢你的解释,但是应该调用 join() 吗?
  • 糟糕,是的,当然“加入循环”需要结束 th。现在已经修好了。
猜你喜欢
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 2019-10-20
  • 1970-01-01
相关资源
最近更新 更多