最近学习了一些C++11的文章,急着动手玩一玩。这次,打算利用boost的thread实现一个线程类,维护一个任务队列,以便可以承载非常灵活的调用。这个线程类可以方便的为后面的线程池打好基础。线程池还是动态均衡,没有什么别的。由于minGW 4.7 对 C++11 thread 不支持,所以采用 boost 代替,linux 下是支持的,只是名字空间不同而已,套路都一样。先上代码:
- #include <iostream>
- #include <boost/thread/thread.hpp>
- #include <boost/thread/mutex.hpp>
- #include <functional>
- #include <list>
- #include <atomic>
- #include <vector>
- #include <stdlib.h>
- #include <time.h>
- #include <assert.h>
- #include <memory>
- //This class defines a class contains a thread, a task queue
- class cpp11_thread
- {
- public:
- cpp11_thread()
- :m_b_is_finish(false)
- ,m_pthread(nullptr)
- {
- }
- ~cpp11_thread()
- {
- if (m_pthread != nullptr)
- delete m_pthread;
- m_list_tasks.clear();
- }
- public:
- //wait until this thread is terminated;
- void join() {
- terminate();
- if (m_pthread!=nullptr)
- m_pthread->join();
- }
- //wait until this thread has no tasks pending.
- void wait_for_idle()
- {
- while(load())
- boost::this_thread::sleep(boost::posix_time::milliseconds(200));
- }
- //set the mask to termminiate
- void terminate() {m_b_is_finish = true; m_cond_incoming_task.notify_one();}
- //return the current load of this thread
- size_t load()
- {
- size_t sz = 0;
- m_list_tasks_mutex.lock();
- sz = m_list_tasks.size();
- m_list_tasks_mutex.unlock();
- return sz;
- }
- //Append a task to do
- size_t append(std::function< void (void) > func)
- {
- if (m_pthread==nullptr)
- m_pthread = new boost::thread(std::bind(&cpp11_thread::run,this));
- size_t sz = 0;
- m_list_tasks_mutex.lock();
- m_list_tasks.push_back(func);
- sz = m_list_tasks.size();
- //if there were no tasks before, we should notidy the thread to do next job.
- if (sz==1)
- m_cond_incoming_task.notify_one();
- m_list_tasks_mutex.unlock();
- return sz;
- }
- protected:
- std::atomic< bool> m_b_is_finish; //atomic bool var to mark the thread the next loop will be terminated.
- std::list<std::function< void (void)> > m_list_tasks; //The Task List contains function objects
- boost::mutex m_list_tasks_mutex; //The mutex with which we protect task list
- boost::thread *m_pthread; //inside the thread, a task queue will be maintained.
- boost::mutex m_cond_mutex; //condition mutex used by m_cond_locker
- boost::condition_variable m_cond_incoming_task; //condition var with which we notify the thread for incoming tasks
- protected:
- void run()
- {
- // loop wait
- while (!m_b_is_finish)
- {
- std::function< void (void)> curr_task ;
- bool bHasTasks = false;
- m_list_tasks_mutex.lock();
- if (m_list_tasks.empty()==false)
- {
- bHasTasks = true;
- curr_task = *m_list_tasks.begin();
- }
- m_list_tasks_mutex.unlock();
- //doing task
- if (bHasTasks)
- {
- curr_task();
- m_list_tasks_mutex.lock();
- m_list_tasks.pop_front();
- m_list_tasks_mutex.unlock();
- }
- if (!load())
- {
- boost::unique_lock< boost::mutex> m_cond_locker(m_cond_mutex);
- boost::system_time const timeout=boost::get_system_time()+ boost::posix_time::milliseconds(5000);
- if (m_cond_locker.mutex())
- m_cond_incoming_task.timed_wait(m_cond_locker,timeout);//m_cond_incoming_task.wait(m_cond_locker);
- }
- }
- }
- };
- //the thread pool class
- class cpp11_thread_pool
- {
- public:
- cpp11_thread_pool(int nThreads)
- :m_n_threads(nThreads)
- {
- assert(nThreads>0 && nThreads<=512);
- for (int i = 0; i< nThreads ;i++)
- m_vec_threads.push_back(std::shared_ptr<cpp11_thread>(new cpp11_thread()));
- }
- ~cpp11_thread_pool()
- {
- }
- public:
- //total threads;
- size_t count(){return m_vec_threads.size();}
- //wait until all threads is terminated;
- void join()
- {
- for_each(m_vec_threads.begin(),m_vec_threads.end(),[this](std::shared_ptr<cpp11_thread> & item)
- {
- item->terminate();
- item->join();
- });
- }
- //wait until this thread has no tasks pending.
- void wait_for_idle()
- {
- int n_tasks = 0;
- do
- {
- if (n_tasks)
- boost::this_thread::sleep(boost::posix_time::milliseconds(200));
- n_tasks = 0;
- for_each(m_vec_threads.begin(),m_vec_threads.end(),[this,&n_tasks](std::shared_ptr<cpp11_thread> & item)
- {
- n_tasks += item->load();
- });
- }while (n_tasks);
- }
- //set the mask to termminiate
- void terminate()
- {
- for_each(m_vec_threads.begin(),m_vec_threads.end(),[this](std::shared_ptr<cpp11_thread> & item)
- {
- item->terminate();
- });
- }
- //return the current load of this thread
- size_t load(int n)
- {
- return (n>=m_vec_threads.size())?0:m_vec_threads[n]->load();
- }
- //Append a task to do
- void append(std::function< void (void) > func)
- {
- int nIdx = -1;
- unsigned int nMinLoad = -1;
- for (unsigned int i=0;i<m_n_threads;i++)
- {
- if (nMinLoad> m_vec_threads[i]->load())
- {
- nMinLoad = m_vec_threads[i]->load();
- nIdx = i;
- }
- }
- assert(nIdx>=0 && nIdx<m_n_threads);
- m_vec_threads[nIdx]->append(func);
- }
- protected:
- //NO. threads
- int m_n_threads;
- //vector contains all the threads
- std::vector<std::shared_ptr<cpp11_thread> > m_vec_threads;
- };
- //a function which will be executed in sub thread.
- void hello()
- {
- //sleep for a while
- boost::this_thread::sleep(boost::posix_time::milliseconds(rand()%900+100));
- std::cout <<
- "Hello world, I'm a function runing in a thread!"
- << std::endl;
- }
- //a class has a method, which will be called in a thread different from the main thread.
- class A
- {
- private:
- int m_n;
- public:
- A(int n)
- :m_n(n)
- {}
- ~A(){}
- public:
- void foo (int k)
- {
- //sleep for a while
- boost::this_thread::sleep(boost::posix_time::milliseconds(rand()%900+100));
- std::cout <<"n*k = "<<k*m_n<<std::endl;
- m_n++;
- }
- };
- //let's test the thread.
- int main()
- {
- cpp11_thread_pool thread(2);
- srand((unsigned int)time(0));
- A a(1),b(2),c(3);
- int nsleep = rand()%900+100;
- //append a simple function task
- thread.append(&hello);
- //append lamda
- thread.append
- (
- [&nsleep]()
- {
- boost::this_thread::sleep(boost::posix_time::milliseconds(nsleep));
- std::cout<<"I'm a lamda runing in a thread"<<std::endl;
- }
- );
- //append object method with copy-constructor(value-assignment)
- thread.append(std::bind(&A::foo,a,10));
- thread.append(std::bind(&A::foo,b,11));
- thread.append(std::bind(&A::foo,c,12));
- thread.append(std::bind(&A::foo,a,100));
- //append object method with address assignment, will cause the objects' member increase.
- thread.append(std::bind(&A::foo,&a,10));
- thread.append(std::bind(&A::foo,&b,11));
- thread.append(std::bind(&A::foo,&c,12));
- thread.append(std::bind(&A::foo,&a,100));
- //wait for all tasks done.
- thread.wait_for_idle();
- //kill
- thread.terminate();
- //wait for killed
- thread.join();
- //test function
- std::function < void (void) > func1 = &hello;
- std::function < void (void) > func2 = &hello;
- if (func1.target<void (void) >()!=func2.target<void (void)>())
- return 0;
- else
- return 1;
- }
程序输出:
- Hello world, I'm a function runing in a thread!
- I'm a lamda runing in a thread
- n*k = 22
- n*k = 10
- n*k = 36
- n*k = 100
- n*k = 22
- n*k = 10
- n*k = 36
- n*k = 200
- Process returned 0 (0x0) execution time : 2.891 s
- Press any key to continue.
下面来看看代码。首先是线程类。
第13-99行是线程类。该类实现了一个带任务队列的线程模型。关键部件是62行的std::list<std::function< void (void)> > m_list_tasks; ,这个fifo 用来承载顺序在子线程运行的任务。任务通过48行的append方法进行追加,64行m_list_tasks_mutex是一个mutex,来保护队列的进出。65行定义的线程对象boost::thread在构造函数中初始化并运行,绑定了本对象的run方法。线程得以运行的关键是run方法,在71-99行定义。该方法首先判断是否有pending的任务,有的话就弹出来执行。如果任务做完了,则使用67行定义的条件变量m_cond_incoming_task 进行wait, 直到新的任务到来,在第56行触发条件,激活队列。
线程类还提供了一些方法,比如load()返回队列的大小,以及terminate终止线程。而后,转到线程池。线程池采用最简单的策略,即直接分配给最空闲的线程。
有了上述封装,main函数就简单多了。可以append几乎所有的东西到线程池,这是以前简单的利用 virtual function 很难做到的。