Разработка многопоточных программ – задача нетривиальная. Локализовать место ошибки сложно, отладка затруднена, иногда даже возникает желание все свалить на ошибки в сторонних библиотеках и/или компилятор/операционную систему. Так делать не надо. Конечно вероятность такая есть, но она несравненно меньше того, что виновником ошибки являетесь именно вы.
Например приложение работает хорошо, ну скажем в 95 случаях из 100, но в оставшихся пяти оно может зависнуть или вылететь с segmentation fault. А может упасть на таком ассерте:
/usr/include/boost/thread/pthread/mutex.hpp:51: void boost::mutex::lock(): Assertion `!pthread_mutex_lock(&m)' failed.
Посмотрим на следующий код:
#include <iostream> #include <queue> #include <boost/thread.hpp> class Worker { public: Worker() : Shutdown_(false) , Thread_(boost::thread(boost::bind(&Worker::ThreadFunction, this))) { } ~Worker() { { boost::mutex::scoped_lock lock(Mutex_); Shutdown_ = true; } Condition_.notify_one(); Thread_.join(); } void AddTask(int taskId) { { boost::mutex::scoped_lock lock(Mutex_); std::cout << "Adding task " << taskId << std::endl; Tasks_.push(taskId); } Condition_.notify_one(); } private: void ThreadFunction() { while (true) { boost::mutex::scoped_lock lock(Mutex_); if (!Tasks_.empty()) { std::cout << "Processing task " << Tasks_.front() << std::endl; Tasks_.pop(); continue; } if (Shutdown_) { return; } Condition_.wait(lock); } } private: bool Shutdown_; std::queue<int> Tasks_; boost::thread Thread_; boost::mutex Mutex_; boost::condition_variable Condition_; }; int main(int argc, char* argv[]) { Worker worker; for (int i = 1; i !=6; ++i) { worker.AddTask(i); } return 0; }
Здесь приведен простой класс, объект которого обрабатывает некоторые задачи в параллельном потоке. Из основного потока функцией AddTask можно добавить задачу в очередь и известить об этом параллельный поток. Поток проснувшись, возьмет из очереди задачу, обработает ее и если задач больше нет – уснет. В деструкторе, прежде чем объект будет уничтожен происходит ожидание обработки всех задач в очереди.
На первый взгляд все прозрачно, но в некоторых случаях, мы вылетим по ассерту из boost (и даже можем начать грешить на его разработчиков) или зависнем, но повторюсь – ошибка здесь наша.
Проблема в списке инициализации. Как мы помним, конструирование членов класса происходит в порядке их объявления. В нашем случае сначала будет создан объект типа boost::thread и только после него boost::mutex. Если звезды сложатся удачно, то запуск параллельного потока займет некоторое время, за которое в основном потоке будет сконструирован мютекс и программа будет работать. Если со звездами не сложиться, то поток начав работу попробует заблокировать мютекс который еще не сконструирован, со всеми вытекающими. Решение простое – изменить положение членов в списке инициализации:
boost::mutex Mutex_; boost::condition_variable Condition_; boost::thread Thread_;