C++ 常用线程池对比

303 阅读4分钟

前言

当前罗列了以下几个线程池相关的库进行对比:

  • Poco
  • boost
  • std::jthread

Poco 线程池

Poco::Runnable

 class Runnable
 {
 public:
     virtual void run() = 0;
 };

Poco::Task

 class Task: public Runnable, public RefCountedObject
 {
 public:
     enum TaskState
     {
         TASK_IDLE,
         TASK_STARTING,
         TASK_RUNNING,
         TASK_CANCELLING,
         TASK_FINISHED
     };
 
     Task(const std::string& name);
     const std::string& name() const;
     
     float progress() const;
     
     virtual void cancel();
     bool isCancelled() const;
     TaskState state() const;
 
     virtual void runTask() = 0;
 
 protected:
     bool sleep(long milliseconds);
         /// If the task is cancelled while it is sleeping,
         /// sleep() will return immediately and the return
         /// value will be true. If the time interval
         /// passes without the task being cancelled, the
         /// return value is false.
         ///
         /// A Task should use this method in favor of Thread::sleep().
 
     void setProgress(float progress);
 
     virtual void postNotification(Notification* pNf);
         /// Posts a notification to the task manager's
         /// notification center.
         ///
         /// A task can use this method to post custom
         /// notifications about its progress.
 };
  • 支持设置任务进度
  • 支持取消任务(任务状态变化)
  • 支持发送通知 Notification
  • 支持可中断的 sleep。取消任务时 sleep 会立即返回
  • 不支持在任意位置判断是否取消
  • 不支持在任意位置设置任务进度
  • 不支持将取消事件传递给其它模块内部使用,其它模块内部在工作时无法立即感知取消事件的发生

Poco::Thread::current

  • 支持设置线程名称
  • 支持判断线程是否结束
  • 支持可中断的 trySleep。调用 wakeUp 时可唤醒该函数,使该函数立即返回
  • 支持线程本地存储

Poco::ThreadPool

  • 支持动态线程池大小。可设置线程池动态大小、空闲多久将线程销毁(销毁时机:默认每添加32个任务触发一次销毁机制)
  • 池中无空闲线程时,添加任务将抛异常 NoThreadAvailableException
  • 不管理 Poco::Runnable 生命周期,任务对象结束后需要手动释放该对象

Poco::TaskManager

  • 支持订阅发布模式 NotificationCenter,外部可订阅任意感兴趣的通知 Notification
  • 管理了 Poco::Task 对象列表及其生命周期。任务结束时该对象将从列表中移除。当对象引用计数为 0 时销毁
  • 使用 Poco::ThreadPool 做为线程池
  • 池中无空闲线程时,添加任务将抛异常 NoThreadAvailableException
  • 不支持任务队列

Boost 线程池

boost::this_thread

  • 支持通知线程中断

关于通知线程中断

 // 可通过线程对象的interrupt函数,通知该线程中断
 boost::thread t(foo);
 t.interrupt();
 
 // 以下函数在调用时都可以立即感知到中断设置,并抛出异常boost::thread_interrupted
 boost::thread::join()
 boost::thread::timed_join()
 boost::thread::try_join_for()
 boost::thread::try_join_until()
 boost::condition_variable::wait()
 boost::condition_variable::timed_wait()
 boost::condition_variable::wait_for()
 boost::condition_variable::wait_until()
 boost::condition_variable_any::wait()
 boost::condition_variable_any::timed_wait()
 boost::condition_variable_any::wait_for()
 boost::condition_variable_any::wait_until()
 boost::thread::sleep()
 boost::this_thread::sleep_for()
 boost::this_thread::sleep_until()
 boost::this_thread::interruption_point()

boost::asio::thread_pool

  • 不支持动态线程池大小,仅支持固定大小
  • 支持任务队列。通过 boost::asio::post 函数,向队列中添加任务

std::jthread

(C++20及更新版本支持)

  • std::jthread 对象析构时会自动调用 join等待其线程退出
  • 支持通知线程停止

为什么不往 std::thread 添加新接口,而是引入了一个新的类?

因为 std::jthread 为了实现上述新功能,带来了额外的性能开销。

关于通知线程停止

 // 为了实现通知线程停止,一共引入了2个概念
 // 1. std::stop_source: 停止事件的发送者
 // 2. std::stop_token: 停止事件的观察者
 
 void thread_entry(std::stop_token stoken)
 {
     for (;;)
     {
         std::this_thread::sleep_for(300ms);
         if (stoken.stop_requested())
         {
             return; // 观察到停止事件,立即结束线程
         }
     }
 }
 
 std::jthread jt(thread_entry);
 std::stop_source ss;
 std::stop_token st;
 bool b = false;
 
 ss = jt.get_stop_source(); // 可以将【停止事件的发送者】传参到任何线程中供调用
 st = jt.get_stop_token(); // 可以在线程内部获取观察者,以观察停止事件是否被触发,也可以在线程以外观察
 
 // 以下两行代码是等价的。get_stop_source返回std::jthread对象内部执有的发送者
 b = ss.request_stop();
 b = jt.request_stop(); // 其内部就是调用的上一行代码
 
 // 观察是否执行了停止请求。该接口用于在线程中执行,判断是否需要结束当前线程
 b = st.stop_requested();