Как вызвать метод класса с потоком?
Мои занятия:
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;
}
Звоню с основного, только выкладываю соответствующую часть. 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 wrapper из объекта, я получаю эту ошибку:
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'
Вот один из способов решения насущных проблем, которые я могу найти в вашем коде. Я также добавил std::
везде, потому что использование пространства имен std плохо.
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();
}
Давайте начнем здесь, потому что это самая прямая проблема. Шаблон для вызова функции-члена с помощью std :: thread:
std::thread(ptr-to-member-func, ptr-to-object, member-func-args...);
По сути вы должны предоставить объект, который станет this
при выполнении loaddata()
, И это называется «указатель» по причине. 😉
Также emplace_back()
является функцией пересылки, которая принимает только аргументы конструктора объекта, который вы хотите поместить в вектор. Явное построение std::thread(...)
здесь побеждает цель.
Это тоже просто. Вы должны убедиться, что потоки могут выполнять всю свою работу и правильно выходить. Это значит звонить join()
в какой-то момент, который будет блокировать, пока поток не будет завершен. Это особенно важно, если у вас все еще работают потоки, когда вы хотите выйти из всей программы.
Обратите внимание, что вызов join()
внутри цикла for, как в ответе ΔλЛ, не сработает. Если вы сделаете это так, вы создадите один поток, дождитесь его завершения, а затем перейдите к следующему потоку. Это запутанный способ выполнения однопоточного исполнения.
Небольшое напоминание о том, как работает вектор (и в основном все остальные контейнеры STD). Он выделяет блок памяти и использует его для предметов, которые вы 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
объекты больше не затрагиваются.
Почему я сказал, что это необязательно? th
собирается перераспределить так же, как ts
, Тем не менее, вы не держитесь за ссылки или ссылки на ваши объекты потока. Неважно, если они будут признаны недействительными. А также std::thread
подвижный, то есть он прекрасно переживет перераспределение. С другой стороны, выделение памяти является потенциально дорогостоящей операцией, и в этом случае ее тривиально избежать.
Посмотри на std::async
для более высокого уровня подхода к параллелизму. Потоки — это конструкция очень низкого уровня. Если вы можете, лучше избегать прямого обращения с ними.
Вы создаете свой указатель как временную переменную, размещенную в стеке, и emplace_back
это в ваш вектор. Когда вы выходите из for
итерация цикла, дескриптор выполняется и 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
ваши темы после окончания загрузки данных.