This article is a companion article of C ++ socket web crawler (1). How can I write web crawlers with fewer threads?
Source code: http://files.cnblogs.com/magicsoar/ThreadPoolProject.rar
* C ++ 11 is required and compiled in vs2013
Running Effect
Background
When a traditional task is received as a thread, every time we receive a task, we create a thread, execute the task, and destroy the thread,
The time used in these three processes is recorded as T1, T2, and T3 respectively.
The time used by the task only occupies T2/(T1 + T2 + T3), which is very inefficient when the time used by the task itself is very short.
In addition, the number of threads that can be created by the operating system is usually limited, and there is no limit to the number of threads that can be created.
In the thread pool, we usually create m threads in advance and put them in the idle container. When a task comes temporarily, the thread pool selects a thread from the idle thread to execute the task,
After the execution is complete, put it back into the idle container.
C ++ 11
In C ++ 11, C ++ provides a very high abstraction for threads and does not provide priority control and other functions. You need to call std: thread :: native_handle (), get native thread object
The platform-specific operations are run, but the consistency of std: thread code on different platforms is lost.
Therefore, the secondary encapsulation of std: thread is implemented in the project, and basic priority control is provided.
Project Overview
There is a main thread in the project, that is, the thread created during the program can obtain the task from the user, and there is also a management thread for thread pool thread scheduling, there are also several Idle threads created during thread pool initialization for task execution.
The project has the following types:
Task: the Task class contains the Task priority and a pure virtual Run method. We need to derive the Task and write the Task to be completed to the Run method.
MyThread: thread class, which encapsulates the thread of C ++ 11. Each thread can be associated with a Task object and Run its Run method.
BusyThreadContainer: Working container class, which uses std: list <MyThread *> to store working threads.
IdleThreadContainer: idle container class, which uses std: vector <MyThread *> to store Idle threads.
TaskContainer: The Task container class, which is implemented using priority_queue <Task *> to store all users for adding unexecuted tasks.
MyThreadPool: A thread pool class used to obtain and manage tasks from users and schedule threads in the thread pool.
The class diagram is as follows:
Task class
namespace{ enum PRIORITY { MIN = 1, NORMAL = 25, MAX = 50 };}class Task{ public: Task() { } void SetPriority(int priority) { if (priority>(PRIORITY::MAX)) { priority = (PRIORITY::MAX); } else if (priority>(PRIORITY::MAX)) { priority = (PRIORITY::MIN); } } virtual void Run() = 0;protected: int priority_;};
Void SetPriority (int priority): sets the priority of a thread. The value ranges from 1 to 50. A greater value indicates a higher priority.
Virtual void run () = 0: the method of thread execution. You need to rewrite it as your own method.
MyThread class
class MyThread{ friend bool operator==(MyThread my1, MyThread my2); friend bool operator!=(MyThread my1, MyThread my2);public: MyThread(MyThreadPool *pool); void Assign(Task *Task); void Run(); void StartThread(); int getthreadid(); void setisdetach(bool isdetach); private: MyThreadPool *mythreadpool_; static int s_threadnumber; bool isdetach_; Task *task_; int threadid_; std::thread thread_;};
Method:
MyThread (MyThreadPool * pool): constructs a MyThread object and associates itself with the specified thread pool.
Void Assign (Task * Task): associate a Task with this thread.
Void Run (): Call the Run method of the Task. After the Run method of the Task is completed, you can migrate yourself from the working container back to the idle container.
Void StartThread (): The Run method of the execution thread, that is, the Run method of the Task is executed.
Int getthreadid (): gets the thread ID.
Void setisdetach (bool isdetach): sets whether the thread is join or detach during running.
BusyThreadContainer class
class BusyThreadContainer{ public: BusyThreadContainer(); ~BusyThreadContainer(); void push(MyThread *m); std::list<MyThread*>::size_type size(); void erase(MyThread *m);private: std::list<MyThread*> busy_thread_container_; typedef std::list<MyThread*> Container; typedef Container::iterator Iterator;};
Void push (MyThread * m): Put a thread into the working container
Void erase (MyThread * m): deletes a specified thread.
Std: list <MyThread *>: size_type size (): returns the size of the working container.
IdleThreadContainer class
class IdleThreadContainer{ public: IdleThreadContainer(); ~IdleThreadContainer(); std::vector<MyThread*>::size_type size(); void push(MyThread *m); void assign(int n,MyThreadPool* m); MyThread* top(); void pop(); void erase(MyThread *m);private: std::vector<MyThread*> idle_thread_container_; typedef std::vector<MyThread*> Container; typedef Container::iterator Iterator;};
~ IdleThreadContainer ();: analyzes threads in idle containers.
Void push (MyThread * m): puts a thread back into the idle container.
Void assign (int n, MyThreadPool * m): Creates threads associated with thread pool m and puts them in idle containers.
MyThread * top (): returns the thread at the top of the idle container.
Void pop (): The thread at the top of the idle container is displayed.
Void erase (MyThread * m): deletes a specified thread.
TaskContainer class
class TaskContainer{public: TaskContainer(); ~TaskContainer(); void push(Task *); Task* top(); void pop(); std::priority_queue<Task*>::size_type size();private: std::priority_queue<Task*> task_container_;};
Void push (Task *): puts a Task into the Task container.
Task * top (): return the Task at the top of the Task container
Void pop (): Bring up the thread at the top of the task container
Std: priority_queue <Task *>: size_type size (): returns the size of the Task container.
MyThreadPool class
class MyThreadPool{public: MyThreadPool(){} MyThreadPool(int number); ~MyThreadPool(); void AddTask(Task *Task,int priority); void AddIdleThread(int n); void RemoveThreadFromBusy(MyThread *myThread); void Start(); void EndMyThreadPool();private: BusyThreadContainer busy_thread_container_; IdleThreadContainer idle_thread_container_; bool issurvive_; TaskContainer task_container_; std::thread thread_this_; std::mutex busy_mutex_; std::mutex idle_mutex_; std::mutex task_mutex_; int number_of_thread_;
};
MyThreadPool (int number): Construct MyThreadPool and create idle containers containing number of threads
Void AddTask (Task * Task, int priority): Add a Task with priority of priority to the Task container.
Void AddIdleThread (int n): Creates n Idle threads to idle containers.
Void RemoveThreadFromBusy (MyThread * myThread): deletes a thread from the working container and moves it back to the idle container.
Void Start (): determines whether there is any idle thread. If there is any idle thread, the task is proposed from the task container and put into the idle container for execution.
Void EndMyThreadPool (): ends the running of the thread pool.
MyTask class derived from Task
class MyTask :public Task{ friend bool operator<(MyTask &lv,MyTask &rv) { return lv.priority_ < rv.priority_; }public: MyTask(); ~MyTask(); virtual void Run(); void setdata(int d);private: int data_;};
MyTask::MyTask(){}MyTask::~MyTask(){}void MyTask::setdata(int d){ data_ = d;}void MyTask::Run(){ std::cout << "Hello I am "<<data_ << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1));}
Friend bool operator <(MyTask & lv, MyTask & rv): used to determine the position of a task in the task container
Run: custom Run Method
Void setdata (int d): Set Data
Key code analysis:
VoidMyThread: Run ()
void MyThread::Run(){ cout <<"Thread:"<< threadid_ << " run "; task_->Run(); mythreadpool_->RemoveThreadFromBusy(this);}
The Run method of the Task is called. After the Run method of the Task is completed, the thread pool is notified to move itself from the working container back to the idle container.
VoidMyThread: StartThread ()
void MyThread::StartThread(){ thread_ = thread(&MyThread::Run, this); if (isdetach_ == true) thread_.detach(); else thread_.join();}
Bind the Run method of MyThread to thread _. this indicates the first implicit parameter of the Run method of the class.
Then, based on the isdetach value, determine whether to detach () or join ()
Void MyThreadPool: RemoveThreadFromBusy (MyThread *MyThread)
void MyThreadPool::RemoveThreadFromBusy(MyThread *myThread){ busy_mutex_.lock(); cout << "Thread:" << myThread->getthreadid()<< " remove from busylist" << endl; busy_thread_container_.erase(myThread); busy_mutex_.unlock(); idle_mutex_.lock(); idle_thread_container_.push(myThread); idle_mutex_.unlock();}
Remove a thread from the task container and put it back into the idle container,
Use busy_mutex _ and idle_mutex _ to lock and unlock data to ensure data consistency.
MyThreadPool: MyThreadPool (intNumber)
MyThreadPool::MyThreadPool(int number){ issurvive_ = true; number_of_thread_ = number; idle_thread_container_.assign(number, this); thread_this_ =thread(&MyThreadPool::Start, this); thread_this_.detach();}
MyThreadPool constructor: creates a number of Idle threads and containers, and creates a management thread thread_this for thread pool thread scheduling.
VoidMyThreadPool: Start ()
void MyThreadPool::Start(){ while (true) { if (issurvive_==false) { busy_mutex_.lock(); if (busy_thread_container_.size()!=0) { busy_mutex_.unlock(); continue; } busy_mutex_.unlock(); break; } idle_mutex_.lock(); if (idle_thread_container_.size() == 0) { idle_mutex_.unlock(); continue; } idle_mutex_.unlock(); task_mutex_.lock(); if (task_container_.size() == 0) { task_mutex_.unlock(); continue; } Task *b = task_container_.top();; task_container_.pop(); task_mutex_.unlock(); idle_mutex_.lock(); MyThread *mythread = idle_thread_container_.top();; idle_thread_container_.pop(); mythread->Assign(b); idle_mutex_.unlock(); busy_mutex_.lock(); busy_thread_container_.push(mythread); busy_mutex_.unlock(); mythread->StartThread(); }}
The Start method corresponding to the management thread has an endless loop, which constantly judges whether there are tasks in the task container, and whether there are Idle threads to execute tasks.
The task container proposes to extract a idle thread from the idle thread and bind it to execute the task. At the same time, the thread is moved from the idle container to the working container.
When the thread pool wants to end running, that is, if keep VE is false, first determine whether the working container is empty. If it is not empty, it indicates that a task is being executed by the thread, thread Pool cannot end running
Otherwise, the thread pool can be stopped and an endless loop exists.
IntMain ()
Int main () {MyThreadPool mythreadPool (10); MyTask j [50]; for (int I = 0; I <50; I ++) {j [I]. setdata (I) ;}for (int I = 0; I <50; I ++) {mythreadPool. addTask (& j [I], I) ;}int I; // press 100 to add a task // press-1 to end the thread pool while (true) {cin> I; if (I = 100) {MyTask j; j. setdata (I); mythreadPool. addTask (& j, I) ;}if (I =-1) {mythreadPool. endMyThreadPool (); break;} system ("pause ");}
Creates a thread pool containing 10 Idle threads and 50 MyTask tasks, and puts them in the thread pool for running.
In a loop, you can enter 100 to add another task to the thread pool to wait for running. Input-1 to end the running of the thread pool.
The running result is as follows:
Postscript for thread pool usage
The thread pool is not omnipotent. The thread pool reduces the impact of creating and destroying threads on the task. However, if the task runs for a long time, the overhead is equivalent to the overhead of the task itself, which can be ignored. We can also
Select the "instant creation, instant destruction" Policy
The thread pool is usually suitable for the following scenarios:
(1) The number of tasks processed per unit time is large, and the execution time of each task is short.
(2) For tasks with high real-time requirements, if a task is created after it is accepted and then executed, it may not meet the real-time requirements. Therefore, the thread pool must be used for pre-creation.