在 C++ 中使用线程调用方法

Call a method with thread in C++

如何使用线程调用 class 方法?

我的class:

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 wrapper 并且再次出现错误: 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'

您正在将指针创建为一个临时的、堆栈分配的变量,并将其 emplace_back 放入您的向量中。当您退出 for 循环迭代时,将执行 desctrutor 并调用 std::terminate 以终止 运行 线程。

解决此问题的一种方法是改用 std::vector<std::thread*>

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你的线程。

这是解决我在您的代码中发现的眼前问题的一种方法。我还在各处添加了 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]

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

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

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

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

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 相同的内容。但是,您不会保留任何指向线程对象的引用或指针。他们是否无效并不重要。并且 std::thread 是可移动的,即它将在重新分配后继续存在。另一方面,内存分配是一个潜在的昂贵操作,在这种情况下避免它是微不足道的。

P.S.

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