简体   繁体   English

我应该如何改进线程池以使其更加线程安全?

[英]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:考虑:

  1. Thread calling runThread sees syncQueue is empty.线程调用runThread看到syncQueue为空。
  2. Thread calling appendTask adds job to the queue and calls notify_one .线程调用appendTask将作业添加到队列并调用notify_one There is no thread to notify.没有要通知的线程。
  3. Thread calling 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM