Implementation of thread pool based on pthread

Source: Internet
Author: User
Tags posix

In the recent process of doing a project, you need to use a thread pool to implement asynchronous processing of tasks. That is, the thread pool contains the pre-created thread, and the client submits the task to the thread pool, and the thread in the thread pool gets and executes the task. For the Pthread line libraries used by the project, we designed and implemented a simple thread pool.

Before introducing the implementation of the thread pool, first tidy up some of the interfaces of the Pthread library. Pthread is a thread implementation that is proposed by POSIX and is widely supported by various UNIX platforms. The interface for creating threads is

Pthread_create (pthread_t* thread, const pthread_attr_t* attr, void * (func) (void*), void* Arg)

In order to synchronize between threads, Pthread provides a mechanism for locking and conditional variables, respectively, pthread_mutex_t and pthread_cond_t and their related functions. The combination of the two can be used to implement the classic operating system of the pipe process mechanism. Among them, the most critical function is

Pthread_cond_wait (pthread_cond_t* cond, pthread_mutex_t* Lock)

This function must be called at the time of the thread's hold lock lock. After calling this function, the calling thread atomically discards the lock and blocks it to the condition variable cond, preventing other threads from changing the condition while the operation is in progress. After the thread is awakened, the thread will be re-engaged in the competition for lock lock, one volume to the lock, and the thread to start execution from the next statement of the function call. It is worth noting that at this point the thread has been re-acquired to the lock.
The thread exits by calling the

Pthread_cancel (pthread_t pid)

Send a signal to the thread to implement. A signal is a mechanism that UNIX provides for communication between processes or threads, and when a thread receives a signal, the signal corresponding to the domain is flagged, and the thread processes the received signal as it exits from the system state to the user's state. In addition, if a thread is blocked on some system calls such as Read,write, the thread will wake up and begin to signal processing, and if it does not exit after the signal processing is completed, it will normally exit the system call and return EINTR, and some operating system will restart the system call that is interrupted. The signal used to terminate the thread is a real-time signal, that is, the signal receives the same number of times as it was sent, and the signal processing has a higher priority. There are two types of exits after a thread receives a signal, an asynchronous exit, where the thread exits immediately when the signal is received, regardless of where it is executed. The other is to delay the exit, that is, after the termination signal is signaled, until the specific execution point and then exit. POSIX gives a set of functions that must contain execution points, which are handled in the following general way:

Check the terminating tag, if it is marked, exit immediately, atomically set exit mode to exit asynchronously
normal function handling, including blocked system calls
Check the termination tag, if it is marked, exit immediately, atomically set exit mode for deferred exit

Refer to the "Advanced Programming for UNIX Environment" book for specific interfaces for Pthread. However, there are a few things to emphasize about thread exit:

1. A thread can use Pthread_cleanup_push to register a function that executes when a thread exits, and the function does not contain checkpoints. Therefore, the first statement that the function call executes as a thread guarantees that the function is registered.
2. In C + +, when a thread exits (including cancel and call Pthread_exit), the destructor of the object on the constructed stack is guaranteed to be called. However, if the object is not fully constructed, it does not.
3. The Pthread_mutex_lock function does not necessarily include checkpoints (at least Linux is like this). This way, if a thread is blocked from acquiring a lock, it is not necessarily awakened when the pthread_cancel signal is received. Therefore, any thread that is on exit must release all locks it holds to prevent other threads from being deadlocked. Because thread exits guarantee the invocation of an object destructor, it is a good idea to encapsulate the lock's acquisition into the object's constructor:

1 class mutexgetlock{2 public: 3     mutexgetlock (pthread_mutex_t* _lock): Lock (_lock)   {pthread_mutex_ Lock (lock);}; 4     ~mutexgetlock () {Pthread_mutex_unlock (lock);}; 5 Private: 6     pthread_mutex_t* lock; 7};     

Get locks can be used

Mutexgetlock Get_lock (&lock);

Note, however, that you do not add input and output statements such as printf in the constructor of this class, because the read and write operations contain checkpoints, which may cause the thread to exit when the object is partially constructed, so that the destructor cannot be executed, resulting in a deadlock.

The following begins to describe the implementation of the thread pool. One of the basic components required to implement the thread pool is the blocking queue, which is used for thread acquisition tasks in the thread pool of the client submission task. In the case where the upper bound of an element in the queue is not set, the blocking queue can be implemented with a lock with a condition variable:

Class blockingqueue{    public:        t* dequeue () {            mutexgetlock Get_lock (lock);            If(no element) {                pthread_cond_wait (empty, lock);}//Get element Pthread_mutex_unlock (lock);} void Enqueue ( {Mutexgetlock Get_lock (lock);//Insert Element pthread_cond_signal (empty);} private: pthread_mutex_t* lock ; pthread_cond_t* Empty;};            

The thread pool interface is as follows:

1 classthreadpool{2 Public: 3 ThreadPool (int core_size, int max_size, blockingqueue<runnable*>* queue = 0); 4 void Submit (runnable*  R), 5 void  shutdown (), 6 void  shutdownimmidiately (), 7 V OID  shutdownabruptly (); 8 ~  ThreadPool (); 9 friend class  workerexiter;10 friend class  Workerrunnable;11 private : enum  pool_state{13  INIT, RUNNING, SHUTDOWN, Shutdown_immi, abrupt, STOPPED14 };15 int  core_size;17 int  max_size;18 blockingqueue<runnable*>*  queue;20 I NT  current_size;21 unordered_map<int, daemonthread*>*  workers;22 + int  next_wid;24 25// State26  pool_state state;27 pthread_mutex_t main_lock;//guard state28  pthread_cond_t term_cond;29 xit32 void onworkerexit (int  wid), daemonthread*  addthread ();       

Where Daemonthread is a simple object-oriented package for pthread. All functions that call the thread pool must first get main_lock, so the thread pool is threaded. Onworkerexit is called when threads in the thread pool exit, and is used to maintain the consistency of the thread pool state. Term_cond for other threads to wait for the thread pool to terminate. The thread provides three ways to terminate:

1. Shutdown: Exits after executing all tasks in the current queue
2. shutdownimmidiately: If the current thread is not performing a task, exit immediately, or exit after executing the current task.
3. shutdownabruptly: All threads exit immediately, regardless of where they are executed.
In addition, the thread pool's expansion strategy is to start with core_size threads, and once the task is added, it is found that the number of tasks in the queue equals the current number of threads, creating a new thread, but the thread will not exceed max_size.
Because all the threads in the thread pool get main_lock first, they are atomic operations, so the most critical element in the threading pools is the correct exit of the wire pool, which focuses on the thread pool exit mechanism.
The worker thread structure for the thread pool is as follows:

 1 class Workerrunnable:public  runnable{2 public : 3 workerrunnable (threadpool* _pool, int _wid, blockingqueue<runnable*>*  _queue): 4  Pool (_pool), wid (_wid), queue (_queue), Run_lock (P Thread_mutex_initializer) {}; 5 void  Run (), 6 void Setthread (daemonthread* _thread) {thread =  _thread;}; 7 void  Canceli FIdel (); 8 void  Cancelnow () 9 ~  workerrunnable () {};10 private : threadpool*  pool; int  wid;13 Blo ckingqueue<runnable*>*  queue;14 daemonthread*  thread;15 pthread_mutex_t run_lock;//If thread is Running, lock Run_lock, then pool can tell it with try_lock16 runnable*  gettask ();     

Among them, the most critical is the run_lock. After a thread obtains a new task from the queue, it acquires the lock first and then executes the task. In this way, other threads can use Pthread_try_lock to get the thread to perform a task, and the thread cannot formally start a new task until the action on that thread is completed. Cancelifidel is implemented with this strategy:

void workerrunnable::cancelifidel{        if (Pthread_try_lock (&Run_lock)) {            Pthread_cancel (thread- >getpid ());        }        }  

The three exit logic implementations of the thread pool are as follows:

1. Shutdown: Set thread pool status, prohibit submit call to commit new task. Then, insert current_size Special exit task Exit_task into the task queue, and once the worker has read the task, it exits, so that all threads can be guaranteed to get an exit task and then exit. Then, the thread that calls shutdown blocks to the term_cond condition variable waiting for the thread pool to exit. Because Onworkerexit is called when each thread exits, it wakes up all threads that are blocked to Term_cond if a worker thread exits and discovers that it is already the last thread.
2. shutdownimmidiately: Set the thread pool state to prohibit the submission of new tasks. Then call Cancelifidel to notify all the threads that are not performing the task to exit. For a thread that is performing a task, it checks the thread pool state and begins execution of the exit logic before it finishes executing the current task to get the next task. The logic of Term_cond is the same as shutdown.
3. shutdownabruptly: Call Cancel (Cancelnow ()) for all threads. If there is an exit point in the currently executing task, it exits, and if not, it waits until the current task finishes executing before exiting.

Based on the following logic, the basic execution logic for a worker thread is as follows:

1     void Workerrunnable::run () {2         pthread_cleanup_push (...);//ensure thread exits when calling Threadpool::onworkerexit         3  4         while (True) {5             runnable* task = gettask (); 6             if (task = = Exit_task) {7                             Pthread_testcancel (); task-> }15 runnable* gettask () {if(the thread pool has handled shutdown_immi or abrupt status) {return } runnable* task = queue->dequeue (); return task;22     

Implementation of thread pool based on pthread

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.