Рассмотрим следующую реализацию тривиального пула потоков, написанного на C++ 14.Безопасное уничтожение пула потоков
Заметим, что каждый поток спит, пока он не был уведомлен разбудить - или какой-то ложный звонок прозвенел - и следующий предикат принимает true
:
std::unique_lock<mutex> lock(this->instance_mutex_);
this->cond_handle_task_.wait(lock, [this] {
return (this->destroy_ || !this->tasks_.empty());
});
Кроме того, обратите внимание, что объект ThreadPool
использует элемент данных destroy_
, чтобы определить, был ли он уничтожен - вызван деструктор. Переключение этого элемента данных на true
будет уведомлять каждый рабочий поток о том, что пришло время завершить свою текущую задачу, а любая другая задача с очередью затем синхронизируется с потоком, который уничтожает этот объект; в дополнение к запрету функции-члена enqueue
.
Для вашего удобства, реализация деструктора ниже:
ThreadPool::~ThreadPool() {
{
std::lock_guard<mutex> lock(this->instance_mutex_); // this line.
this->destroy_ = true;
}
this->cond_handle_task_.notify_all();
for (auto &worker : this->workers_) {
worker.join();
}
}
Q: Я не понимаю, почему это необходимо, чтобы заблокировать мьютекс объекта, а переключая destroy_
к true
в деструкторе. Кроме того, необходимо ли это только для установки его значения или же оно необходимо для доступа к его стоимости?
BQ: Может ли эта реализация пула потоков быть улучшена или оптимизирована при сохранении ее первоначальной цели; пул потоков, который может объединить N
количество потоков и распределить задачи для их выполнения одновременно? реализация бассейн
Этот поток разветвляется от Jakob Progsch's C++11 thread pool repository с основательным шагом кода через понимать конечную цель его реализации и некоторые субъективные изменения стиля.
Я представляю себя параллельным программированием, и многое еще предстоит узнать - я начинающий параллельный программист, поскольку он стоит прямо сейчас. Если мои вопросы не сформулированы правильно, пожалуйста, внесите соответствующие исправления в ваш предоставленный ответ. Более того, если ответ может быть ориентирован на клиента, который впервые вводится в параллельное программирование, тогда это было бы лучше - для меня и для любых других новичков.
Флаг уничтожения является частью предиката *. Правило №1 протокола condition-var/mutex/предикат: никогда не изменяйте * и даже не проверяйте * данные предиката, если только они не защищены защитой мьютекса. Помните, что мьютекс защищает данные предиката; а не переменная условия. Если бы флаг уничтожения мог быть изменен атомарно, он был бы освобожден. Как показано, это не так. Показанный код правильный, и хотя он может казаться излишним, это правильный шаблон для изучения. – WhozCraig
Я думаю, что основная путаница здесь в том, что мьютекс часто является излишне тяжелой операцией для того, что на самом деле является синхронизирующей памятью или атомной операцией чтения/записи, обе из которых доступны как часть стандарта C++ 11 ... IMHO, использующий мьютекс, был «безопасным» способом предотвращения доброкачественных гонок данных перед атомикой, но теперь это не самый оптимальный способ ... – Jason
Я рекомендую книгу Энтони Уильямса * C++ Concurrency In Action * любому, кто изучает параллельное программирование на C++ , он охватывает параллелизм C++ 11, включая библиотеку потоков и атомов, модель памяти C++ и т. д. В разделе 9.1 есть код для простого потока threadpool. В ней эквивалентная переменная - это * std :: atomic_bool done *, и деструктор просто устанавливает * done = true *, позволяя компилятору решить, требуется ли что-либо, кроме хранилища в памяти (оно не находится на x86 из-за его Total Store Модель памяти заказа). см. http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – amdn