[英]How should I improve a thread pool to make it more thread safe?
I am currently learning the basics about thread pooling.我目前正在学习有关线程池的基础知识。 Here are some code blocks that I have written taking into account some examples found on the web:
以下是我根据网络上的一些示例编写的一些代码块:
SyncQueue.h同步队列.h
#ifndef SYNC_QUEUE_H
#define SYNC_QUEUE_H
#include <list>
#include <mutex>
#include <iostream>
template<typename T>
class SyncQueue {
public:
SyncQueue();
~SyncQueue();
SyncQueue(const SyncQueue&) = delete;
SyncQueue& operator=(const SyncQueue &) = delete;
void append(const T& data);
T& get();
unsigned long size();
bool empty();
private:
std::list<T> queue;
std::mutex myMutex;
};
#endif
SyncQueue.cpp同步队列.cpp
#include "SyncQueue.h"
template<typename T>
SyncQueue<T>::SyncQueue():
queue(),
myMutex() {}
template<typename T>
SyncQueue<T>::~SyncQueue() {}
template<typename T>
void SyncQueue<T>::append(const T& data) {
std::unique_lock<std::mutex> l(myMutex);
queue.push_back(data);
}
template<typename T>
T& SyncQueue<T>::get() {
std::unique_lock<std::mutex> l(myMutex);
T& res = queue.front();
queue.pop_front();
return res;
}
template<typename T>
unsigned long SyncQueue<T>::size() {
std::unique_lock<std::mutex> l(myMutex);
return queue.size();
}
template<typename T>
bool SyncQueue<T>::empty() {
std::unique_lock<std::mutex> l(myMutex);
return queue.empty();
}
template class SyncQueue<std::function<void()>>;
ThreadPool.h线程池.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <atomic>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>
#include "SyncQueue.h"
class ThreadPool {
public:
ThreadPool(unsigned long thrdAmount = 0);
virtual ~ThreadPool();
void appendTask(std::function<void()> func);
unsigned long pendingTasks();
private:
void runThread();
unsigned int myThrdAmount;
std::atomic<bool> done;
SyncQueue<std::function<void()>> syncQueue;
std::vector<std::thread> threads;
std::condition_variable myCondVar;
std::mutex myMutex;
};
#endif
ThreadPool.cpp线程池.cpp
#include "ThreadPool.h"
ThreadPool::ThreadPool(unsigned long thrdAmount):
myThrdAmount(0),
done(false),
syncQueue(),
threads(),
myCondVar(),
myMutex() {
if (thrdAmount > 0) {
myThrdAmount = thrdAmount;
} else {
myThrdAmount = std::thread::hardware_concurrency();
}
for (unsigned int i = 0; i < myThrdAmount; i++) {
threads.push_back(std::thread(&ThreadPool::runThread, this));
}
}
ThreadPool::~ThreadPool() {
done = true;
myCondVar.notify_all();
for (auto& thrd: threads) {
if (thrd.joinable()) {
thrd.join();
}
}
}
void ThreadPool::appendTask(std::function<void()> func) {
syncQueue.append(func);
{
std::unique_lock<std::mutex> l(myMutex);
myCondVar.notify_one();
}
}
unsigned long ThreadPool::pendingTasks() {
return syncQueue.size();
}
void ThreadPool::runThread() {
while (!done) {
if (syncQueue.empty()) {
std::unique_lock<std::mutex> l(myMutex);
myCondVar.wait(l);
continue;
}
syncQueue.get()();
}
}
main.cpp主程序
#include <unistd.h>
#include <iostream>
#include "ThreadPool.h"
void print() {
std::cout << "Hello World!" << std::endl;
}
int main(int argc, char const *argv[]) {
ThreadPool p;
for (int i = 0; i < 20; i++) {
p.appendTask(print);
}
std::cout << "Pending: " << p.pendingTasks() << std::endl;
sleep(5);
for (int i = 0; i < 20; i++) {
p.appendTask(print);
}
return 0;
}
Despite all the operations on a SyncQueue
are locked by a mutex and the condition variable of the ThreadPool
is also protected by a mutex, the code often results in undefined behaviours.尽管
SyncQueue
上的所有操作都由互斥锁锁定并且ThreadPool
的条件变量也受互斥锁保护,但代码通常会导致未定义的行为。
That said, can you please explain me where the code is lacking of thread safety?也就是说,你能解释一下代码哪里缺乏线程安全吗? How should I improved it?
我应该如何改进它?
void ThreadPool::appendTask(std::function<void()> func) {
syncQueue.append(func);
{
std::unique_lock<std::mutex> l(myMutex);
myCondVar.notify_one();
}
}
void ThreadPool::runThread() {
while (!done) {
if (syncQueue.empty()) {
std::unique_lock<std::mutex> l(myMutex);
myCondVar.wait(l);
continue;
}
syncQueue.get()();
}
}
The problem is that myMutex
doesn't actually protect anything.问题是
myMutex
实际上并没有保护任何东西。 So your code has a catstrophic race condition around waiting for the queue.所以你的代码在等待队列时有一个灾难性的竞争条件。
Consider:考虑:
runThread
sees syncQueue
is empty.runThread
看到syncQueue
为空。appendTask
adds job to the queue and calls notify_one
.appendTask
将作业添加到队列并调用notify_one
。 There is no thread to notify.runThread
finally gets the lock on myMutex
and waits on the condition variable, but the queue isn't empty.runThread
最终获得了myMutex
上的锁并等待条件变量,但队列不为空。 It is absolutely vital that the condition variable you use for waiting be associated with the mutex that protects the predicate you are waiting for.您用于等待的条件变量与保护您正在等待的谓词的互斥锁相关联是绝对重要的。 The entire purpose of a condition variable is to allow you to atomically unlock the predicate and wait for a signal without a race condition.
条件变量的全部目的是允许您以原子方式解锁谓词并等待没有竞争条件的信号。 But you buried the predicate inside the
syncQueue
, defeating the condition variable's lock handling logic.但是您将谓词隐藏在
syncQueue
,从而syncQueue
了条件变量的锁处理逻辑。
You can fix this race condition by making all calls into syncQueue
under the protection of the myMutex
mutex.您可以通过在
myMutex
互斥锁的保护下将所有调用发送到syncQueue
来解决此竞争条件。 But it might make a lot more sense to make syncQueue
waitable.但让
syncQueue
等待可能更有意义。 This may make it harder to shut down the thread pool though.不过,这可能会使关闭线程池变得更加困难。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.