【问题标题】:c++ work queues with blocking带有阻塞的 c++ 工作队列
【发布时间】:2014-11-13 12:53:33
【问题描述】:

这个问题应该比我上几个问题简单一点。我在我的程序中实现了以下工作队列:

池.h:

// tpool class
// It's always closed. :glasses:
#ifndef __POOL_H
#define __POOL_H
class tpool {
    public:
        tpool( std::size_t tpool_size );
        ~tpool();
        template< typename Task >
        void run_task( Task task ){
        boost::unique_lock< boost::mutex > lock( mutex_ );
            if( 0 < available_ ) {
                --available_;
                io_service_.post( boost::bind( &tpool::wrap_task, this, boost::function< void() > ( task ) ) );
            }
        }
    private:
        boost::asio::io_service io_service_;
        boost::asio::io_service::work work_;
        boost::thread_group threads_;
        std::size_t available_;
        boost::mutex mutex_;
        void wrap_task( boost::function< void() > task );
};
extern tpool dbpool;
#endif

pool.cpp:

#include <boost/asio/io_service.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include "pool.h"
tpool::tpool( std::size_t tpool_size ) : work_( io_service_ ), available_( tpool_size ) {
    for ( std::size_t i = 0; i < tpool_size; ++i ){
        threads_.create_thread( boost::bind( &boost::asio::io_service::run, &io_service_ ) );
    }
}
tpool::~tpool() {
    io_service_.stop();
    try {
        threads_.join_all();
    }
    catch( ... ) {}
}
void tpool::wrap_task( boost::function< void() > task ) {
    // run the supplied task
    try {
        task();
    } // suppress exceptions
    catch( ... ) {
    }
    boost::unique_lock< boost::mutex > lock( mutex_ );
    ++available_;
}
tpool dbpool( 50 );

但问题是,并非我对run_task() 的所有调用都由工作线程完成。我不确定是因为它没有进入队列还是因为创建它的线程退出时任务消失了。

所以我的问题是,我有什么特别需要给boost::thread 让它等到队列解锁的吗?进入队列的任务的预期生命周期是多少?当创建它们的线程退出时,任务是否超出范围?如果是这样,我该如何防止这种情况发生?

编辑:我对我的代码进行了以下更改:

template< typename Task >
void run_task( Task task ){ // add item to the queue
    io_service_.post( boost::bind( &tpool::wrap_task, this, boost::function< void() > ( task ) ) );
}

现在看到所有条目都输入正确。但是,我还有一个挥之不去的问题:添加到队列中的任务的生命周期是多少?一旦创建它们的线程退出,它们就不再存在了吗?

【问题讨论】:

    标签: c++ multithreading threadpool boost-asio boost-thread


    【解决方案1】:

    嗯。这真的很简单;您拒绝发布的任务!

    template< typename Task >
    void run_task(task task){
        boost::unique_lock<boost::mutex> lock( mutex_ );
        if(0 < available_) {
            --available_;
            io_service_.post(boost::bind(&tpool::wrap_task, this, boost::function< void() > ( task )));
        }
    }
    

    请注意,lock“等待”直到互斥锁不属于线程。这可能已经是这种情况,并且可能在available_ 已经为 0 时。现在行

    if(0 < available_) {
    

    这一行只是条件。这不是“神奇的”,因为您将mutex_ 锁定。 (程序甚至不知道mutex_available_ 之间存在关系)。所以,如果available_ &lt;= 0 你会跳过发布工作。


    解决方案 #1

    您应该使用io_service 为您排队。这可能是您最初想要实现的目标。 io_service 不是跟踪“可用”线程,而是为您完成工作。您可以通过在尽可能多的线程上运行io_service 来控制它可以使用的线程数。很简单。

    由于io_service 已经是线程安全的,你可以不用锁。

    #include <boost/asio.hpp>
    #include <boost/thread.hpp>
    #include <iostream>
    
    // tpool class
    // It's always closed. :glasses:
    #ifndef __POOL_H
    #define __POOL_H
    class tpool {
        public:
            tpool( std::size_t tpool_size );
            ~tpool();
    
            template<typename Task>
            void run_task(Task task){
                io_service_.post(task);
            }
        private:
            // note the order of destruction of members
            boost::asio::io_service io_service_;
            boost::asio::io_service::work work_;
    
            boost::thread_group threads_;
    };
    
    extern tpool dbpool;
    #endif
    
    #include <boost/asio/io_service.hpp>
    #include <boost/thread/mutex.hpp>
    #include <boost/bind.hpp>
    #include <boost/thread.hpp>
    //#include "pool.h"
    
    tpool::tpool(std::size_t tpool_size) : work_(io_service_) {
        for (std::size_t i = 0; i < tpool_size; ++i)
        {
            threads_.create_thread( 
                    boost::bind(&boost::asio::io_service::run, &io_service_) 
                );
        }
    }
    
    tpool::~tpool() {
        io_service_.stop();
    
        try {
            threads_.join_all();
        }
        catch(...) {}
    }
    
    void foo() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    void bar() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    
    int main() {
        tpool dbpool(50);
    
        dbpool.run_task(foo);
        dbpool.run_task(bar);
    
        boost::this_thread::sleep_for(boost::chrono::seconds(1));
    }
    

    出于关闭目的,您需要启用“清除”io_service::work 对象,否则您的池将永远不会退出。


    解决方案 #2

    不要使用io_service,而是使用条件变量滚动您自己的队列实现,以通知工作线程有新工作正在发布。同样,worker 的数量由组中的线程数决定。

    #include <boost/thread.hpp>
    #include <boost/phoenix.hpp>
    #include <boost/optional.hpp>
    
    using namespace boost;
    using namespace boost::phoenix::arg_names;
    
    class thread_pool
    {
      private:
          mutex mx;
          condition_variable cv;
    
          typedef function<void()> job_t;
          std::deque<job_t> _queue;
    
          thread_group pool;
    
          boost::atomic_bool shutdown;
          static void worker_thread(thread_pool& q)
          {
              while (auto job = q.dequeue())
                  (*job)();
          }
    
      public:
          thread_pool() : shutdown(false) {
              for (unsigned i = 0; i < boost::thread::hardware_concurrency(); ++i)
                  pool.create_thread(bind(worker_thread, ref(*this)));
          }
    
          void enqueue(job_t job) 
          {
              lock_guard<mutex> lk(mx);
              _queue.push_back(std::move(job));
    
              cv.notify_one();
          }
    
          optional<job_t> dequeue() 
          {
              unique_lock<mutex> lk(mx);
              namespace phx = boost::phoenix;
    
              cv.wait(lk, phx::ref(shutdown) || !phx::empty(phx::ref(_queue)));
    
              if (_queue.empty())
                  return none;
    
              auto job = std::move(_queue.front());
              _queue.pop_front();
    
              return std::move(job);
          }
    
          ~thread_pool()
          {
              shutdown = true;
              {
                  lock_guard<mutex> lk(mx);
                  cv.notify_all();
              }
    
              pool.join_all();
          }
    };
    
    void the_work(int id)
    {
        std::cout << "worker " << id << " entered\n";
    
        // no more synchronization; the pool size determines max concurrency
        std::cout << "worker " << id << " start work\n";
        this_thread::sleep_for(chrono::seconds(2));
        std::cout << "worker " << id << " done\n";
    }
    
    int main()
    {
        thread_pool pool; // uses 1 thread per core
    
        for (int i = 0; i < 10; ++i)
            pool.enqueue(bind(the_work, i));
    }
    

    【讨论】:

    • 我明白了...那么 wrap_task 是不是必要的?我问,因为我使用 wrap_task 允许我发布包含 boost::bind 参数的任务,例如 dbpool.run_task( boost::bind( some_func, some_arg ) );通常会这样调用: some_func( some_arg );
    • 对于我之前错过的评论:您仍然可以使用run_task 成员函数发布绑定表达式。 (只是计数现在是队列的责任,而不是任务线程的责任。我认为这更好)。干杯。
    • @Sehe 您能否详细说明“出于关闭目的,您需要启用“清除”io_service::work 对象,否则您的池将永远不会退出。”?
    • @AlessandroTeruzzi 只要在 io_service 上注册了 work,它就永远不会自发完成。因此,对于干净的关闭,通常会动态创建 work 实例(想想 unique_ptr&lt;work&gt;boost::optional&lt;work&gt;
    猜你喜欢
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多