執行緒池概念
假設完成一項任務需要的時間=建立執行緒時間T1+執行緒執行任務時間T2+銷燬執行緒時間T3,如果T1+T3的時間遠大於T2,通常就可以考慮採取執行緒池來提高伺服器的效能
thread pool
就是執行緒的一種使用模式,一個執行緒池中維護著多個執行緒等待接收管理者分配的可併發執行的任務。
- 避免了處理短時間任務時建立與銷燬執行緒的代價
- 既保證核心的充分利用,又能防止過度排程
- 可用執行緒數量應該取決於可用的併發處理器、處理器核心、記憶體、網路sockets的數量
執行緒池組成部分
- 執行緒池管理器(thread pool):建立、銷燬執行緒池
- 工作執行緒(pool wroker):在沒有任務時處於等待狀態,迴圈讀取並執行任務佇列中的任務
- 任務(task):抽象一個任務,主要規定任務的入口、任務執行完後的收尾工作、任務的執行狀態等
- 任務佇列(task queue):存放沒有處理的任務,提供一種緩衝機制
C風格ThreadPool
1. 抽象一個任務
將待處理的任務抽象成task結構:
typedef struct task {
void* (*run)(void* args); // abstract a job function that need to run
void* arg; // argument of the run function
struct task* next; // point to the next task in task queue
} task_t;
2. 任務佇列
threadpool
中用first
和last
指標指向首尾兩個任務task
結構體保證每個task
都能指向任務佇列中下一個task
typedef struct task {
void* (*run)(void* args); // abstract a job function that need to run
void* arg; // argument of the run function
struct task* next; // point to the next task in task queue
} task_t;
typedef struct threadpool {
condition_t ready; // condition & mutex
task_t* first; // fist task in task queue
task_t* last; // last task in task queue
int counter; // total task number
int idle; // idle task number
int max_threads; // max task number
int quit; // the quit flag
} threadpool_t;
3. 執行緒安全的問題
設計了condition_t
類來實現安全併發:
typedef struct condition {
/**
* 互斥鎖
*/
pthread_mutex_t pmutex;
/**
* 條件變數
*/
pthread_cond_t pcond;
} condition_t;
提供對應的介面:
/**
* 初始化
*/
int condition_init(condition_t* cond);
/**
* 加鎖
*/
int condition_lock(condition_t* cond);
/**
* 解鎖
*/
int condition_unlock(condition_t* cond);
/**
* 條件等待
*
* pthread_cond_wait(cond, mutex)的功能有3個:
* 1) 呼叫者執行緒首先釋放mutex
* 2) 然後阻塞, 等待被別的執行緒喚醒
* 3) 當呼叫者執行緒被喚醒後,呼叫者執行緒會再次獲取mutex
*/
int condition_wait(condition_t* cond);
/**
* 計時等待
*/
int condition_timedwait(condition_t* cond, const timespec* abstime);
/**
* 啟用一個等待該條件的執行緒
*
* 1) 作用: 傳送一個訊號給另外一個處於阻塞等待狀態的執行緒, 使其脫離阻塞狀態繼續執行
* 2) 如果沒有執行緒處在阻塞狀態, 那麼pthread_cond_signal也會成功返回, 所以需要判斷下idle thread的數量
* 3) 最多隻會給一個執行緒發訊號,不會有「驚群現象」
* 4) 首先根據執行緒優先順序的高低確定傳送給哪個執行緒訊號, 如果優先順序相同則優先發給等待最久的執行緒
* 5) 重點: pthread_cond_wait必須放在lock和unlock之間, 因為他要根據共享變數的狀態決定是否要等待; 但是pthread_cond_signal既可以放在lock和unlock之間,也可以放在lock和unlock之後
*/
int condition_signal(condition_t *cond);
/**
* 喚醒所有等待執行緒
*/
int condition_broadcast(condition_t *cond);
/**
* 銷燬
*/
int condition_destroy(condition_t *cond);
4. 執行緒池的實現
4.1 初始化一個執行緒池
僅僅是初始化了condition
和mutex
,還有一些執行緒池的屬性。但是任務佇列是空的,而且此時也一個執行緒都沒有。
// initialize the thread pool
void threadpool_init(threadpool_t* pool, int threads_num) {
int n_status = condition_init(&pool ->ready);
if (n_status == 0) {
printf("Info: initialize the thread pool successfully!\n");
} else {
printf("Error: initialize the thread pool failed, status:%d\n", n_status);
}
pool->first = NULL;
pool->last = NULL;
pool->counter = 0;
pool->idle = 0;
pool->max_threads = threads_num;
pool->quit = 0;
}
4.2 向執行緒池中新增任務,並分配給它一個執行緒
首先構建task
結構體,然後將其加入任務佇列。
- 如果當前有空閒執行緒那麼直接呼叫空閒執行緒執行函式
- 如果無空閒執行緒且當前執行緒數未滿時建立一個新的執行緒執行任務
- 如果無空閒執行緒且當前執行緒數已滿時,任務會呆在任務佇列中等待執行緒池釋放出空閒執行緒
// add a task to thread pool
void threadpool_add_task(threadpool_t* pool, void* (*run)(void *arg), void* arg) {
// create a task
task_t* new_task = reinterpret_cast<task_t*>(malloc(sizeof(task_t)));
new_task->run = run;
new_task->arg = arg;
new_task->next = NULL;
// lock the condition
condition_lock(&pool->ready);
// add the task to task queue
if (pool->first == NULL) {
pool->first = new_task;
} else { // else add to the last task
pool->last->next = new_task;
}
pool->last = new_task;
/*
* after you add a task to task queue, you need to allocate it to a thread:
* (1)if idle thread num > 0: awake a idle thread
* (2)if idle thread num = 0 & thread num does not reach maximum: create a new thread to run the task
*/
if (pool->idle > 0) {
// awake a thread that wait for longest time
condition_signal(&pool->ready);
} else if (pool->counter < pool->max_threads) {
// define a tid to get the thread identifier that we are going to create
pthread_t tid;
/*
* pthread_create():
* (1)thread identifier
* (2)set the pthread attribute
* (3)the function that thread is going to run
* (4)the args of run func
*
* A realistic limit of thread num is 200 to 400 threads
* https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxbd00/ptcrea.htm
*/
pthread_create(&tid, NULL, thread_routine, pool);
pool->counter++;
} else { // when (idle == 0 & counter = max_threads), then wait
printf("Warning: no idle thread, please wait...\n");
}
condition_unlock(&pool->ready);
}
5. 執行緒的執行過程
5.1 如果任務佇列為空
// when task queue is empty, then block 2 second to get the new task
// If timeout, then destroy the thread
while (pool->first == NULL && !pool->quit) {
printf("Info: thread %ld is waiting for a task\n", (u_int64_t)pthread_self());
// get the system time
clock_gettime(CLOCK_REALTIME, &abs_name);
abs_name.tv_sec += 2;
int status;
status = condition_timedwait(&pool->ready, &abs_name); // block for 2 second
if (status == ETIMEDOUT) {
printf("Info: thread %ld wait timed out\n", (u_int64_t)pthread_self());
timeout = true;
break;
}
}
...
// if visit task queue timeout(means no task in queue), quit destory the thread
if (timeout) {
pool->counter--;
condition_unlock(&pool->ready);
break; // destroy the thread
}
5.2 如果任務佇列非空
// when the thread run the task, we should unlock the thread pool
if (pool->first != NULL) {
// get the task from task queue
task_t* t = pool->first;
pool->first = t->next;
// unlock the thread pool to make other threads visit task queue
condition_unlock(&pool->ready);
// run the task run func
t->run(t->arg);
free(t);
// lock
condition_lock(&pool->ready);
}
5.3 沒有任務且收到退出訊號
// when task queue is clean and quit flag is 1, then destroy the thread
if (pool->quit && pool->first == NULL) {
pool->counter--;
// 若執行緒池中執行緒數為0,通知等待執行緒(主執行緒)全部任務已經完成
if (pool->counter == 0) {
condition_signal(&pool->ready);
}
condition_unlock(&pool->ready);
break; // destroy the thread
}
6. 程式碼
condition.h:
#ifndef CONDITION_H_
#define CONDITION_H_
#include <pthread.h>
#include <cstdio>
typedef struct condition {
/**
* 互斥鎖
*/
pthread_mutex_t pmutex;
/**
* 條件變數
*/
pthread_cond_t pcond;
} condition_t;
/**
* 初始化
*/
int condition_init(condition_t* cond);
/**
* 加鎖
*/
int condition_lock(condition_t* cond);
/**
* 解鎖
*/
int condition_unlock(condition_t* cond);
/**
* 條件等待
*
* pthread_cond_wait(cond, mutex)的功能有3個:
* 1) 呼叫者執行緒首先釋放mutex
* 2) 然後阻塞, 等待被別的執行緒喚醒
* 3) 當呼叫者執行緒被喚醒後,呼叫者執行緒會再次獲取mutex
*/
int condition_wait(condition_t* cond);
/**
* 計時等待
*/
int condition_timedwait(condition_t* cond, const timespec* abstime);
/**
* 啟用一個等待該條件的執行緒
*
* 1) 作用: 傳送一個訊號給另外一個處於阻塞等待狀態的執行緒, 使其脫離阻塞狀態繼續執行
* 2) 如果沒有執行緒處在阻塞狀態, 那麼pthread_cond_signal也會成功返回, 所以需要判斷下idle thread的數量
* 3) 最多隻會給一個執行緒發訊號,不會有「驚群現象」
* 4) 首先根據執行緒優先順序的高低確定傳送給哪個執行緒訊號, 如果優先順序相同則優先發給等待最久的執行緒
* 5) 重點: pthread_cond_wait必須放在lock和unlock之間, 因為他要根據共享變數的狀態決定是否要等待; 但是pthread_cond_signal既可以放在lock和unlock之間,也可以放在lock和unlock之後
*/
int condition_signal(condition_t *cond);
/**
* 喚醒所有等待執行緒
*/
int condition_broadcast(condition_t *cond);
/**
* 銷燬
*/
int condition_destroy(condition_t *cond);
#endif // CONDITION_H_
condition.cpp:
#include "condition.h"
// 初始化
int condition_init(condition_t* cond) {
int status;
status = pthread_mutex_init(&cond->pmutex, NULL);
if (status != 0) {
printf("Error: pthread_mutex_init failed, return value:%d\n", status);
return status;
}
status = pthread_cond_init(&cond->pcond, NULL);
if (status != 0) {
printf("Error: pthread_cond_init failed, return value:%d\n", status);
return status;
}
return 0;
}
// 加鎖
int condition_lock(condition_t* cond) {
return pthread_mutex_lock(&cond->pmutex);
}
// 解鎖
int condition_unlock(condition_t* cond) {
return pthread_mutex_unlock(&cond->pmutex);
}
// 條件等待
int condition_wait(condition_t* cond) {
return pthread_cond_wait(&cond->pcond, &cond->pmutex);
}
// 計時等待
int condition_timedwait(condition_t* cond, const timespec* abstime) {
return pthread_cond_timedwait(&cond->pcond, &cond->pmutex, abstime);
}
// 啟用一個等待該條件的執行緒
int condition_signal(condition_t *cond) {
return pthread_cond_signal(&cond->pcond);
}
// 喚醒所有等待執行緒
int condition_broadcast(condition_t *cond) {
return pthread_cond_broadcast(&cond->pcond);
}
// 銷燬
int condition_destroy(condition_t *cond) {
int status;
status = pthread_mutex_destroy(&cond->pmutex);
if (status != 0) {
return status;
}
status = pthread_cond_destroy(&cond->pcond);
if (status != 0) {
return status;
}
return 0;
}
threadpool.h:
#ifndef THREAD_POLL_H_
#define THREAD_POLL_H_
#include "condition.h"
typedef struct task {
void* (*run)(void* args); // abstract a job function that need to run
void* arg; // argument of the run function
struct task* next; // point to the next task in task queue
} task_t;
typedef struct threadpool {
condition_t ready; // condition & mutex
task_t* first; // fist task in task queue
task_t* last; // last task in task queue
int counter; // total task number
int idle; // idle task number
int max_threads; // max task number
int quit; // the quit flag
} threadpool_t;
/**
* initialize threadpool
*/
void threadpool_init(threadpool_t* pool, int threads_num);
/**
* add a task to threadpool
*/
void threadpool_add_task(threadpool_t* pool, void* (*run)(void *args), void* arg);
/**
* destroy threadpool
*/
void threadpool_destroy(threadpool_t* pool);
#endif // THREAD_POLL_H_
Threadpool.cpp:
#include <pthread.h>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <ctime>
#include "threadpool.h"
void *thread_routine(void *arg);
// initialize the thread pool
void threadpool_init(threadpool_t* pool, int threads_num) {
int n_status = condition_init(&pool ->ready);
if (n_status == 0) {
printf("Info: initialize the thread pool successfully!\n");
} else {
printf("Error: initialize the thread pool failed, status:%d\n", n_status);
}
pool->first = NULL;
pool->last = NULL;
pool->counter = 0;
pool->idle = 0;
pool->max_threads = threads_num;
pool->quit = 0;
}
// add a task to thread pool
void threadpool_add_task(threadpool_t* pool, void* (*run)(void *arg), void* arg) {
// create a task
task_t* new_task = reinterpret_cast<task_t*>(malloc(sizeof(task_t)));
new_task->run = run;
new_task->arg = arg;
new_task->next = NULL;
// lock the condition
condition_lock(&pool->ready);
// add the task to task queue
if (pool->first == NULL) {
pool->first = new_task;
} else { // else add to the last task
pool->last->next = new_task;
}
pool->last = new_task;
/*
* after you add a task to task queue, you need to allocate it to a thread:
* (1)if idle thread num > 0: awake a idle thread
* (2)if idle thread num = 0 & thread num does not reach maximum: create a new thread to run the task
*/
if (pool->idle > 0) {
// awake a thread that wait for longest time
condition_signal(&pool->ready);
} else if (pool->counter < pool->max_threads) {
// define a tid to get the thread identifier that we are going to create
pthread_t tid;
/*
* pthread_create():
* (1)thread identifier
* (2)set the pthread attribute
* (3)the function that thread is going to run
* (4)the args of run func
*
* A realistic limit of thread num is 200 to 400 threads
* https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxbd00/ptcrea.htm
*/
pthread_create(&tid, NULL, thread_routine, pool);
pool->counter++;
} else { // when (idle == 0 & counter = max_threads), then wait
printf("Warning: no idle thread, please wait...\n");
}
condition_unlock(&pool->ready);
}
// create a thread to run the task run func
// and the void *arg means the arg passed by pthread_create: pool
void *thread_routine(void *arg) {
struct timespec abs_name;
bool timeout;
printf("Info: create thread, and the thread id is: %ld\n", (u_int64_t)pthread_self());
threadpool_t *pool = reinterpret_cast<threadpool_t *>(arg);
// keep visiting the task queue
while (true) {
timeout = false;
condition_lock(&pool->ready);
pool->idle++;
// when task queue is empty, then block 2 second to get the new task
// If timeout, then destroy the thread
while (pool->first == NULL && !pool->quit) {
printf("Info: thread %ld is waiting for a task\n", (u_int64_t)pthread_self());
// get the system time
clock_gettime(CLOCK_REALTIME, &abs_name);
abs_name.tv_sec += 2;
int status;
status = condition_timedwait(&pool->ready, &abs_name); // block for 2 second
if (status == ETIMEDOUT) {
printf("Info: thread %ld wait timed out\n", (u_int64_t)pthread_self());
timeout = true;
break;
}
}
pool->idle--;
// when the thread run the task, we should unlock the thread pool
if (pool->first != NULL) {
// get the task from task queue
task_t* t = pool->first;
pool->first = t->next;
// unlock the thread pool to make other threads visit task queue
condition_unlock(&pool->ready);
// run the task run func
t->run(t->arg);
free(t);
// lock
condition_lock(&pool->ready);
}
// when task queue is clean and quit flag is 1, then destroy the thread
if (pool->quit && pool->first == NULL) {
pool->counter--;
// 若執行緒池中執行緒數為0,通知等待執行緒(主執行緒)全部任務已經完成
if (pool->counter == 0) {
condition_signal(&pool->ready);
}
condition_unlock(&pool->ready);
break; // destroy the thread
}
// if visit task queue timeout(means no task in queue), quit destory the thread
if (timeout) {
pool->counter--;
condition_unlock(&pool->ready);
break; // destroy the thread
}
condition_unlock(&pool->ready);
}
// if break, destroy the thread
printf("Info: thread %ld quit\n", (u_int64_t)pthread_self());
return NULL;
}
/*
* destroy a thread pool:
* 1) awake all the idle thread
* 2) wait for the running thread to finish
*/
void threadpool_destroy(threadpool_t *pool) {
if (pool->quit) {
return;
}
condition_lock(&pool->ready);
pool->quit = 1;
if (pool->counter > 0) {
if (pool->idle > 0) {
condition_broadcast(&pool->ready);
}
while (pool->counter > 0) {
condition_wait(&pool->ready);
}
}
condition_unlock(&pool->ready);
condition_destroy(&pool->ready);
}
test.cpp:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "threadpool.h"
#define THREADPOOL_MAX_NUM 30
void* mytask(void *arg) {
printf("Info: thread %ld is working on task %d\n", (u_int64_t)pthread_self(), *reinterpret_cast<int*>(arg));
sleep(1);
free(arg);
return NULL;
}
int main(int argc, char* argv[]) {
threadpool_t pool;
threadpool_init(&pool, THREADPOOL_MAX_NUM);
// add task to task queue
for (int i=0; i < 100; i++) {
int *arg = reinterpret_cast<int *>(malloc(sizeof(int)));
*arg = i;
threadpool_add_task(&pool, mytask, arg);
}
threadpool_destroy(&pool);
return 0;
}
編譯執行:
$g++ -g test.cpp threadpool.cpp condition.cpp -o test -std=c++11 -lpthread
$./test
Info: initialize the thread pool successfully!
Info: create thread, and the thread id is: 139898193295104
Info: create thread, and the thread id is: 139898176509696
Info: thread 139898176509696 is working on task 0
Info: create thread, and the thread id is: 139898168116992
Info: create thread, and the thread id is: 139898184902400
Info: create thread, and the thread id is: 139898134546176
Info: create thread, and the thread id is: 139898126153472
Info: create thread, and the thread id is: 139898117760768
Info: thread 139898117760768 is working on task 1
Info: create thread, and the thread id is: 139898100975360
Info: create thread, and the thread id is: 139898092582656
Info: create thread, and the thread id is: 139898084189952
Info: create thread, and the thread id is: 139898159724288
Info: create thread, and the thread id is: 139898109368064
Info: create thread, and the thread id is: 139898067404544
Info: create thread, and the thread id is: 139898059011840
Info: create thread, and the thread id is: 139898050619136
Info: create thread, and the thread id is: 139898042226432
Info: create thread, and the thread id is: 139898033833728
Info: create thread, and the thread id is: 139898025441024
Info: create thread, and the thread id is: 139898017048320
Info: create thread, and the thread id is: 139898008655616
Info: create thread, and the thread id is: 139898075797248
Info: create thread, and the thread id is: 139898000262912
Info: create thread, and the thread id is: 139898142938880
Info: create thread, and the thread id is: 139898151331584
Info: thread 139898159724288 is working on task 2
Info: thread 139898151331584 is working on task 3
Info: create thread, and the thread id is: 139897991870208
Info: create thread, and the thread id is: 139897966692096
Info: create thread, and the thread id is: 139897958299392
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Info: create thread, and the thread id is: 139897949906688
Info: create thread, and the thread id is: 139897983477504
Info: create thread, and the thread id is: 139897975084800
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Info: thread 139898067404544 is working on task 4
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Info: thread 139898168116992 is working on task 5
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Warning: no idle thread, please wait...
Info: thread 139898142938880 is working on task 6
Warning: no idle thread, please wait...
Info: thread 139898042226432 is working on task 7
Warning: no idle thread, please wait...
Info: thread 139897949906688 is working on task 8
Info: thread 139898184902400 is working on task 11
Info: thread 139898134546176 is working on task 13
Info: thread 139898017048320 is working on task 14
Info: thread 139898008655616 is working on task 16
Info: thread 139898193295104 is working on task 18
Info: thread 139898000262912 is working on task 20
Info: thread 139898100975360 is working on task 21
Info: thread 139897983477504 is working on task 9
Info: thread 139897975084800 is working on task 10
Info: thread 139898092582656 is working on task 26
Info: thread 139898050619136 is working on task 24
Info: thread 139897991870208 is working on task 28
Info: thread 139898025441024 is working on task 12
Info: thread 139898084189952 is working on task 15
Info: thread 139898109368064 is working on task 17
Info: thread 139897966692096 is working on task 25
Info: thread 139898075797248 is working on task 19
Info: thread 139898059011840 is working on task 22
Info: thread 139897958299392 is working on task 27
Info: thread 139898033833728 is working on task 29
Info: thread 139898126153472 is working on task 23
Info: thread 139898176509696 is working on task 30
Info: thread 139898117760768 is working on task 31
Info: thread 139898159724288 is working on task 32
Info: thread 139898151331584 is working on task 33
Info: thread 139898067404544 is working on task 34
Info: thread 139898168116992 is working on task 35
Info: thread 139898142938880 is working on task 36
Info: thread 139898042226432 is working on task 37
Info: thread 139897949906688 is working on task 38
Info: thread 139898184902400 is working on task 39
Info: thread 139898000262912 is working on task 40
Info: thread 139898017048320 is working on task 41
Info: thread 139898008655616 is working on task 42
Info: thread 139898134546176 is working on task 43
Info: thread 139898193295104 is working on task 44
Info: thread 139898050619136 is working on task 49
Info: thread 139897991870208 is working on task 50
Info: thread 139898025441024 is working on task 51
Info: thread 139898084189952 is working on task 52
Info: thread 139898109368064 is working on task 53
Info: thread 139898075797248 is working on task 54
Info: thread 139897975084800 is working on task 48
Info: thread 139898100975360 is working on task 45
Info: thread 139898033833728 is working on task 57
Info: thread 139897983477504 is working on task 47
Info: thread 139897966692096 is working on task 55
Info: thread 139897958299392 is working on task 56
Info: thread 139898092582656 is working on task 46
Info: thread 139898126153472 is working on task 58
Info: thread 139898059011840 is working on task 59
Info: thread 139898176509696 is working on task 60
Info: thread 139898117760768 is working on task 61
Info: thread 139898159724288 is working on task 62
Info: thread 139898151331584 is working on task 63
Info: thread 139898067404544 is working on task 64
Info: thread 139898168116992 is working on task 65
Info: thread 139898142938880 is working on task 66
Info: thread 139898042226432 is working on task 67
Info: thread 139897949906688 is working on task 69
Info: thread 139898184902400 is working on task 68
Info: thread 139898000262912 is working on task 70
Info: thread 139898008655616 is working on task 71
Info: thread 139898017048320 is working on task 72
Info: thread 139898050619136 is working on task 73
Info: thread 139898134546176 is working on task 74
Info: thread 139898109368064 is working on task 78
Info: thread 139898100975360 is working on task 80
Info: thread 139897975084800 is working on task 82
Info: thread 139898075797248 is working on task 81
Info: thread 139897966692096 is working on task 85
Info: thread 139897958299392 is working on task 86
Info: thread 139898126153472 is working on task 88
Info: thread 139898059011840 is working on task 89
Info: thread 139898092582656 is working on task 87
Info: thread 139898025441024 is working on task 76
Info: thread 139898084189952 is working on task 77
Info: thread 139898193295104 is working on task 75
Info: thread 139897991870208 is working on task 79
Info: thread 139898033833728 is working on task 83
Info: thread 139897983477504 is working on task 84
Info: thread 139898176509696 is working on task 90
Info: thread 139898117760768 is working on task 91
Info: thread 139898159724288 is working on task 92
Info: thread 139898151331584 is working on task 93
Info: thread 139898067404544 is working on task 94
Info: thread 139898168116992 is working on task 95
Info: thread 139898142938880 is working on task 96
Info: thread 139898042226432 is working on task 97
Info: thread 139897949906688 is working on task 98
Info: thread 139898184902400 is working on task 99
Info: thread 139898000262912 quit
Info: thread 139898008655616 quit
Info: thread 139898017048320 quit
Info: thread 139898050619136 quit
Info: thread 139898134546176 quit
Info: thread 139898109368064 quit
Info: thread 139898100975360 quit
Info: thread 139897966692096 quit
Info: thread 139898126153472 quit
Info: thread 139897975084800 quit
Info: thread 139898075797248 quit
Info: thread 139897958299392 quit
Info: thread 139898059011840 quit
Info: thread 139898092582656 quit
Info: thread 139898025441024 quit
Info: thread 139898084189952 quit
Info: thread 139898193295104 quit
Info: thread 139897991870208 quit
Info: thread 139898033833728 quit
Info: thread 139897983477504 quit
Info: thread 139898176509696 quit
Info: thread 139898117760768 quit
Info: thread 139898159724288 quit
Info: thread 139898151331584 quit
Info: thread 139898067404544 quit
Info: thread 139898168116992 quit
Info: thread 139898142938880 quit
Info: thread 139898042226432 quit
Info: thread 139897949906688 quit
Info: thread 139898184902400 quit
C++03特性的ThreadPool
1. 基於條件變數的執行緒池
threadpool.h:
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <stdio.h>
#include <pthread.h>
#include <functional>
#include <vector>
#include <queue>
class ThreadPool {
public:
typedef void *(WrokerFunc)(void* arg);
struct Task {
WrokerFunc* run;
void* arg;
};
explicit ThreadPool(int thread_num);
~ThreadPool();
void addTask(WrokerFunc* func, void* arg);
private:
std::queue<Task*> task_queue_;
std::vector<pthread_t> thread_list_;
bool is_running_; // note: is_running_不用原子變數或者鎖操作可能存在卡死問題
pthread_mutex_t mutex_;
pthread_cond_t condition_;
static void* thread_routine(void* pool_ptr);
void thread_worker();
};
// =========================implementation=========================
inline ThreadPool::ThreadPool(int thread_num) : is_running_(true) {
pthread_mutex_init(&mutex_, NULL);
pthread_cond_init(&condition_, NULL);
for (int i = 0; i < thread_num; i++) {
pthread_t pid;
pthread_create(&pid, NULL, thread_routine, this);
thread_list_.push_back(pid);
}
}
inline ThreadPool::~ThreadPool() {
pthread_mutex_lock(&mutex_);
is_running_ = false;
pthread_mutex_unlock(&mutex_);
pthread_cond_broadcast(&condition_); // wakeup all threads that block to get task
for (int i = 0; i < thread_list_.size(); i++) {
pthread_join(thread_list_[i], NULL);
}
pthread_cond_destroy(&condition_);
pthread_mutex_destroy(&mutex_);
}
inline void ThreadPool::addTask(WrokerFunc* func, void* arg) {
Task* task = new Task();
task->run = func;
task->arg = arg;
pthread_mutex_lock(&mutex_);
task_queue_.push(task);
pthread_mutex_unlock(&mutex_);
pthread_cond_signal(&condition_);
}
inline void* ThreadPool::thread_routine(void* pool_ptr) {
ThreadPool* pool = static_cast<ThreadPool*>(pool_ptr);
pool->thread_worker();
}
inline void ThreadPool::thread_worker() {
Task* task = NULL;
while (true) {
pthread_mutex_lock(&mutex_);
if (!is_running_) {
pthread_mutex_unlock(&mutex_);
break;
}
if (task_queue_.empty()) {
pthread_cond_wait(&condition_, &mutex_); // 獲取不到任務時阻塞, 直到有新的任務入隊
if (task_queue_.empty()) {
pthread_mutex_unlock(&mutex_);
continue;
}
}
task = task_queue_.front();
task_queue_.pop();
pthread_mutex_unlock(&mutex_);
(*(task->run))(task->arg);
delete task;
task = NULL;
}
// 執行緒池終止時(is_running_ = false)確保任務佇列為空後退出
while (true) {
pthread_mutex_lock(&mutex_);
if (task_queue_.empty()) {
pthread_mutex_unlock(&mutex_);
break;
}
task = task_queue_.front();
task_queue_.pop();
pthread_mutex_unlock(&mutex_);
delete task;
task = NULL;
}
printf("Info: thread[%lu] exit\n", pthread_self());
}
#endif // THREAD_POOL_H
測試程式碼threadpool_test.cpp:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <vector>
#include "threadpool.h"
void* MyTaskFunc(void* arg) {
int* i = static_cast<int*>(arg);
printf("[MyTaskFunc]: thread[%lu] is working on %d\n", pthread_self(), *i);
return NULL;
}
int main() {
ThreadPool pool(10);
for (int i = 0; i < 100; i++) {
int* arg = new int(i);
pool.addTask(&MyTaskFunc, arg);
}
return 0;
}
編譯執行:
$g++ -g threadpool_test.cpp -o threadpool_test -lpthread
$./threadpool_test
[MyTaskFunc]: thread[140224777099008] is working on 0
[MyTaskFunc]: thread[140224793884416] is working on 8
[MyTaskFunc]: thread[140224844240640] is working on 2
Info: thread[140224844240640] exit
[MyTaskFunc]: thread[140224827455232] is working on 4
Info: thread[140224827455232] exit
[MyTaskFunc]: thread[140224810669824] is working on 6
Info: thread[140224810669824] exit
[MyTaskFunc]: thread[140224777099008] is working on 10
Info: thread[140224777099008] exit
[MyTaskFunc]: thread[140224852633344] is working on 1
Info: thread[140224852633344] exit
[MyTaskFunc]: thread[140224835847936] is working on 3
Info: thread[140224835847936] exit
[MyTaskFunc]: thread[140224802277120] is working on 7
Info: thread[140224802277120] exit
Info: thread[140224793884416] exit
[MyTaskFunc]: thread[140224819062528] is working on 5
Info: thread[140224819062528] exit
[MyTaskFunc]: thread[140224785491712] is working on 9
Info: thread[140224785491712] exit
2. 基於訊號量的同步任務佇列
上述的執行緒池無法很好地支援同步任務,因此我們基於訊號量實現了SyncTaskQueue。
sync_task_queue.h:
#ifndef SYNC_TASK_QUEUE_H
#define SYNC_TASK_QUEUE_H
#include <semaphore.h>
#include <vector>
#include "threadpool.h"
class SyncTaskQueue {
public:
struct SyncTask {
ThreadPool::Task task;
sem_t* sem;
};
explicit SyncTaskQueue(ThreadPool* pool_ptr);
~SyncTaskQueue();
void addTask(ThreadPool::WrokerFunc* func, void* sync_task_ptr);
void wait();
private:
ThreadPool* threadpool_;
sem_t sem_;
int sync_task_num_; // 務必保證單執行緒讀寫, 否則需要加鎖
static ThreadPool::WrokerFunc workerFuncWrapper;
};
inline SyncTaskQueue::SyncTaskQueue(ThreadPool* pool_ptr) : threadpool_(pool_ptr), sync_task_num_(0) {
sem_init(&sem_, 0, 0);
}
inline SyncTaskQueue::~SyncTaskQueue() {
// make sure that all task has been finished before destory
if (sync_task_num_ > 0) {
wait();
}
sem_destroy(&sem_);
}
inline void SyncTaskQueue::addTask(ThreadPool::WrokerFunc* func, void* arg) {
sync_task_num_++;
// wrapper the worker function with sem
SyncTask* sync_task = new SyncTask();
sync_task->sem = &(this->sem_);
sync_task->task.run = func;
sync_task->task.arg = arg;
threadpool_->addTask(&workerFuncWrapper, sync_task);
}
inline void SyncTaskQueue::wait() {
while (sync_task_num_) {
int sem_value = 0;
sem_wait(&sem_);
sync_task_num_--;
}
}
inline void* SyncTaskQueue::workerFuncWrapper(void* sync_task_ptr) {
SyncTask* sync_task = static_cast<SyncTask*>(sync_task_ptr);
(*(sync_task->task.run))(sync_task->task.arg);
sem_post(sync_task->sem);
delete sync_task;
}
#endif // SYNC_TASK_QUEUE_H
測試檔案sync_task_queue_test.cpp:
#include <unistd.h>
#include "sync_task_queue.h"
void* MyTaskFunc(void* arg) {
int i = *static_cast<int*>(arg);
printf("[MyTaskFunc]: thread[%lu] is working on %d\n", pthread_self(), i);
sleep(2);
return NULL;
}
int main() {
ThreadPool threadpool(20);
SyncTaskQueue sync_task_queue(&threadpool);
for (int i = 0; i < 15; i++) {
int* arg = new int(i);
sync_task_queue.addTask(&MyTaskFunc, arg);
}
printf("====================================wait for result===================================\n");
sync_task_queue.wait();
}
編譯執行:
$g++ -g sync_task_queue_test.cpp -o sync_task_queue_test -lpthread
$./sync_task_queue_test
[MyTaskFunc]: thread[140349199148800] is working on 0
[MyTaskFunc]: thread[140349266290432] is working on 12
[MyTaskFunc]: thread[140349358610176] is working on 2
[MyTaskFunc]: thread[140349341824768] is working on 3
[MyTaskFunc]: thread[140349333432064] is working on 4
[MyTaskFunc]: thread[140349325039360] is working on 5
[MyTaskFunc]: thread[140349308253952] is working on 6
[MyTaskFunc]: thread[140349299861248] is working on 8
[MyTaskFunc]: thread[140349316646656] is working on 7
[MyTaskFunc]: thread[140349283075840] is working on 9
[MyTaskFunc]: thread[140349291468544] is working on 10
[MyTaskFunc]: thread[140349274683136] is working on 11
[MyTaskFunc]: thread[140349257897728] is working on 13
[MyTaskFunc]: thread[140349249505024] is working on 14
[MyTaskFunc]: thread[140349350217472] is working on 1
====================================wait for result===================================
Info: thread[140349232719616] exit
Info: thread[140349241112320] exit
Info: thread[140349266290432] exit
Info: thread[140349207541504] exit
Info: thread[140349299861248] exit
Info: thread[140349283075840] exit
Info: thread[140349291468544] exit
Info: thread[140349249505024] exit
Info: thread[140349274683136] exit
Info: thread[140349333432064] exit
Info: thread[140349325039360] exit
Info: thread[140349308253952] exit
Info: thread[140349224326912] exit
Info: thread[140349316646656] exit
Info: thread[140349199148800] exit
Info: thread[140349350217472] exit
Info: thread[140349257897728] exit
Info: thread[140349215934208] exit
Info: thread[140349358610176] exit
Info: thread[140349341824768] exit
C++11特性的ThreadPool
傳統C++執行緒池僅能接受特殊的Task(執行函式需要滿足特殊的格式),使用C++11特性的執行緒池可以更好地支援任意型別引數的Task。
1. 程式碼
threadpool.h:
參考自:https://github.com/progschj/ThreadPool/blob/master/ThreadPool.h
另外一種實現:https://github.com/mtrebi/thread-pool/blob/master/include/ThreadPool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <utility>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
explicit ThreadPool(size_t);
template<typename F, typename... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector<std::thread> workers_;
// the task queue
std::queue<std::function<void()>> tasks_;
// synchronization
std::mutex queue_mutex_;
std::condition_variable condition_;
bool stop_;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads) : stop_(false) {
for (size_t i = 0; i < threads; ++i) {
workers_.emplace_back(
[this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex_);
this->condition_.wait(lock,
[this] { return this->stop_ || !this->tasks_.empty(); });
if (this->stop_ && this->tasks_.empty()) {
return;
}
task = std::move(this->tasks_.front());
this->tasks_.pop();
}
task();
}
});
}
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()>> (
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
// don't allow enqueueing after stopping the pool
if (stop_) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks_.emplace([task](){ (*task)(); });
}
condition_.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
stop_ = true;
}
condition_.notify_all();
for (std::thread &worker : workers_) {
worker.join();
}
}
#endif // THREAD_POOL_H
test.cpp:
#include <unistd.h>
#include "threadpool.h"
void mytask(int i) {
printf("Info: thread %ld is working on task %d\n", (u_int64_t)pthread_self(), i);
sleep(1);
return;
}
int main() {
ThreadPool threadpool(20);
for (int i = 0; i < 100; ++i) {
threadpool.enqueue(mytask, i);
}
return 0;
}
編譯執行:
$g++ -g test.cpp -o test -std=c++11 -lpthread
$./test
Info: thread 139679726323456 is working on task 1
Info: thread 139679709538048 is working on task 3
Info: thread 139679717930752 is working on task 2
Info: thread 139679734716160 is working on task 0
Info: thread 139679600432896 is working on task 15
Info: thread 139679684359936 is working on task 6
Info: thread 139679617218304 is working on task 14
Info: thread 139679634003712 is working on task 12
Info: thread 139679667574528 is working on task 8
Info: thread 139679625611008 is working on task 13
Info: thread 139679575254784 is working on task 19
Info: thread 139679659181824 is working on task 9
Info: thread 139679701145344 is working on task 4
Info: thread 139679692752640 is working on task 5
Info: thread 139679592040192 is working on task 17
Info: thread 139679583647488 is working on task 18
Info: thread 139679675967232 is working on task 7
Info: thread 139679642396416 is working on task 11
Info: thread 139679608825600 is working on task 16
Info: thread 139679650789120 is working on task 10
Info: thread 139679684359936 is working on task 21
Info: thread 139679617218304 is working on task 24
Info: thread 139679600432896 is working on task 28
Info: thread 139679709538048 is working on task 23
Info: thread 139679659181824 is working on task 30
Info: thread 139679692752640 is working on task 32
Info: thread 139679583647488 is working on task 34
Info: thread 139679608825600 is working on task 35
Info: thread 139679592040192 is working on task 33
Info: thread 139679634003712 is working on task 25
Info: thread 139679625611008 is working on task 29
Info: thread 139679726323456 is working on task 20
Info: thread 139679701145344 is working on task 31
Info: thread 139679650789120 is working on task 36
Info: thread 139679667574528 is working on task 27
Info: thread 139679575254784 is working on task 37
Info: thread 139679734716160 is working on task 26
Info: thread 139679675967232 is working on task 38
Info: thread 139679717930752 is working on task 22
Info: thread 139679642396416 is working on task 39
Info: thread 139679684359936 is working on task 40
Info: thread 139679692752640 is working on task 45
Info: thread 139679625611008 is working on task 51
Info: thread 139679583647488 is working on task 43
Info: thread 139679659181824 is working on task 44
Info: thread 139679575254784 is working on task 55
Info: thread 139679592040192 is working on task 47
Info: thread 139679617218304 is working on task 41
Info: thread 139679717930752 is working on task 57
Info: thread 139679726323456 is working on task 49
Info: thread 139679634003712 is working on task 50
Info: thread 139679650789120 is working on task 52
Info: thread 139679675967232 is working on task 59
Info: thread 139679667574528 is working on task 54
Info: thread 139679608825600 is working on task 46
Info: thread 139679734716160 is working on task 56
Info: thread 139679600432896 is working on task 48
Info: thread 139679642396416 is working on task 58
Info: thread 139679709538048 is working on task 42
Info: thread 139679701145344 is working on task 53
Info: thread 139679684359936 is working on task 60
Info: thread 139679625611008 is working on task 62
Info: thread 139679692752640 is working on task 61
Info: thread 139679583647488 is working on task 63
Info: thread 139679659181824 is working on task 64
Info: thread 139679575254784 is working on task 65
Info: thread 139679592040192 is working on task 66
Info: thread 139679617218304 is working on task 67
Info: thread 139679717930752 is working on task 68
Info: thread 139679726323456 is working on task 69
Info: thread 139679650789120 is working on task 71
Info: thread 139679634003712 is working on task 70
Info: thread 139679675967232 is working on task 72
Info: thread 139679667574528 is working on task 73
Info: thread 139679608825600 is working on task 74
Info: thread 139679734716160 is working on task 75
Info: thread 139679642396416 is working on task 77
Info: thread 139679709538048 is working on task 78
Info: thread 139679701145344 is working on task 79
Info: thread 139679600432896 is working on task 76
Info: thread 139679684359936 is working on task 80
Info: thread 139679625611008 is working on task 81
Info: thread 139679583647488 is working on task 83
Info: thread 139679692752640 is working on task 82
Info: thread 139679659181824 is working on task 84
Info: thread 139679575254784 is working on task 85
Info: thread 139679717930752 is working on task 88
Info: thread 139679617218304 is working on task 87
Info: thread 139679592040192 is working on task 86
Info: thread 139679634003712 is working on task 91
Info: thread 139679650789120 is working on task 90
Info: thread 139679667574528 is working on task 93
Info: thread 139679675967232 is working on task 92
Info: thread 139679608825600 is working on task 94
Info: thread 139679726323456 is working on task 89
Info: thread 139679734716160 is working on task 95
Info: thread 139679709538048 is working on task 96
Info: thread 139679642396416 is working on task 97
Info: thread 139679701145344 is working on task 98
Info: thread 139679600432896 is working on task 99
2. 使用方式
2.1 全域性執行緒池 + 非同步任務
建立一個ThreadPool的全域性變數,將所有需要非同步執行的任務丟到該執行緒池中即可:
#include <unistd.h>
#include "threadpool.h"
// 全域性非同步執行緒池
ThreadPool g_threadpool2(20);
int main() {
// 執行非同步任務
g_threadpool2.enqueue(
[] {
sleep(1);
printf("async task done\n");
});
return 0;
}
編譯執行:
$g++ -g test.cpp -o test -std=c++11 -lpthread
$./test
async task done
2.2 全域性執行緒池 + 同步任務
建立一個ThreadPool的全域性變數並新增同步任務,通過std::future
的wait()
方法阻塞等待同步結果,也可以使用get()
方法獲取到函式返回值。
#include <unistd.h>
#include <memory>
#include "threadpool.h"
// 全域性非同步執行緒池
ThreadPool g_threadpool2(20);
int main() {
// 建立同步任務
auto res = g_threadpool2.enqueue(
[] {
sleep(1);
printf("sync task done\n");
});
// 阻塞等待同步結果
res.wait();
return 0;
}
編譯執行:
$g++ -g test.cpp -o test -std=c++11 -lpthread
$./test
sync task done
2.3 區域性執行緒池實現併發同步
建立一個臨時ThreadPool,利用其解構函式完成併發同步任務:
需要注意的是,這種用法已經脫離了執行緒池的初衷(避免處理短時間任務時建立與銷燬執行緒的代價),它的主要用途是實現「多執行緒併發」,常用於併發多個IO請求並等待同步結果。
考慮這個場景:程式碼中僅在某種特殊場景(極少觸發)下需要併發請求多個http連結,一方面我們不希望這些請求影響到程式的業務執行緒池,另一方面我們又不想單獨為這個場景建立一個全域性執行緒池使其大部分時間都在空跑。
2.3這種用法解決了我們「臨時建立執行緒+執行並行任務+銷燬執行緒」的區域性併發問題,避免我們直接在使用者程式碼處直接建立執行緒。
#include <unistd.h>
#include <memory>
#include "threadpool.h"
int main() {
// 建立併發度為5的區域性執行緒池
std::shared_ptr<ThreadPool> threadpool = std::make_shared<ThreadPool>(5);
// 建立30個非同步任務
for (int i = 0; i < 30; i++) {
threadpool->enqueue(
[i] {
sleep(1);
printf("Info: thread %ld is working on task %d\n", (u_int64_t)pthread_self(), i);
});
}
// 阻塞直至獲取同步結果
threadpool.reset();
return 0;
}
編譯執行:
$g++ -g test.cpp -o test -std=c++11 -lpthread
$./test
Info: thread 139811129124608 is working on task 4
Info: thread 139811145910016 is working on task 2
Info: thread 139811137517312 is working on task 3
Info: thread 139811162695424 is working on task 0
Info: thread 139811154302720 is working on task 1
Info: thread 139811129124608 is working on task 5
Info: thread 139811137517312 is working on task 7
Info: thread 139811145910016 is working on task 6
Info: thread 139811162695424 is working on task 8
Info: thread 139811154302720 is working on task 9
Info: thread 139811129124608 is working on task 10
Info: thread 139811137517312 is working on task 11
Info: thread 139811162695424 is working on task 13
Info: thread 139811154302720 is working on task 14
Info: thread 139811145910016 is working on task 12
Info: thread 139811129124608 is working on task 15
Info: thread 139811137517312 is working on task 18
Info: thread 139811145910016 is working on task 19
Info: thread 139811162695424 is working on task 16
Info: thread 139811154302720 is working on task 17
Info: thread 139811129124608 is working on task 21
Info: thread 139811162695424 is working on task 23
Info: thread 139811154302720 is working on task 24
Info: thread 139811145910016 is working on task 22
Info: thread 139811137517312 is working on task 20
Info: thread 139811162695424 is working on task 25
Info: thread 139811154302720 is working on task 26
Info: thread 139811129124608 is working on task 27
Info: thread 139811137517312 is working on task 29
Info: thread 139811145910016 is working on task 28
Reference
[1] https://www.cnblogs.com/ailumiyana/p/10016965.html
[2] https://github.com/progschj/ThreadPool/blob/master/ThreadPool.h
[3] https://blog.csdn.net/qq_34771252/article/details/90319537
[4] https://github.com/lizhenghn123/zl_threadpool/tree/master/ThreadPoolCpp03