目次
1. スレッド プールとは何ですか?
スレッド プールは、プーリング テクノロジの考え方を使用して実装されたスレッド管理テクノロジであり、主にスレッドを再利用し、スレッドとタスクを便利に管理し、スレッドの作成をタスクの実行から分離するために使用されます。スレッド プールを作成して、すでに作成されたスレッドを再利用することで、スレッドの頻繁な作成と破棄によるリソースの消費を削減できます。
2. スレッドプールの役割
- スレッドリソースを再利用します。
- スレッドの作成と破棄のオーバーヘッドを削減します。
- プロデューサ スレッドのタスクは非同期的に処理できます。
- (1 つのタスクではなく) 複数のタスクの実行時間の短縮
- スレッドリソースのオーバーヘッドとCPUリソースのバランスのとれた選択
3. タスクキューの設計
スレッド プールのタスク キューは、配列、リンク リスト、キュー、スタックなどのコンテナーで設計できます。
タスクキュー用に連続した領域をあらかじめ循環タスクキューとして開放しておくことで、頻繁にメモリを割り当てる必要がなくなり、メモリの断片化が軽減されます。
//任务队列
//T是任务
template <typename T>
struct task_queue_t{
task_queue_t(int _count=QUEUECOUNT)
{
if(_count<=0){
throw "queue is zero";
}
max_count=_count;
cur_count=0;
queue=new T[max_count];
if(queue==nullptr)
{
throw bad_alloc();
}
head=0;
tail=0;
}
~task_queue_t()
{
if(queue!=nullptr){
delete queue;
queue=nullptr;
}
}
size_t head; //可读任务的位置
size_t tail; //可写任务的位置
size_t max_count;//最大的任务数量
size_t cur_count;//当前队列中的任务数量
T* queue;
};
4. コンストラクター
template <typename T>
threadpool<T>::threadpool(size_t _thrd_count,size_t _task_size)
{
pool=new pthread_t[thrd_count];
if(pool==nullptr)
started=0;
task_queue=new task_queue_t<T*>(_task_size);
if(task_queue==nullptr)
{
throw bad_alloc();
}
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cond,NULL);
for(int i=0;i<_thrd_count;i++)
{
pthread_create(&pool[i],NULL,routine,this);
thrd_count++;
started++;
}
}
5.プッシュインターフェイスデザイン
template <typename T>
int threadpool<T>::push(T* task){
if(pool==nullptr||task_queue==nullptr){
return -1;
}
if(pthread_mutex_lock(&lock)==-1){
return -1;
}
if(close){
pthread_mutex_unlock(&lock);
return -1;
}
if(task_queue->cur_count==task_queue->max_count){
pthread_mutex_unlock(&lock);
return -1;
}
task_queue->queue[task_queue->tail]=task;
if(pthread_cond_signal(&cond)!=0){
pthread_mutex_unlock(&lock);
return -1;
}
task_queue->tail=(task_queue->tail+1)%task_queue->max_count;
task_queue->cur_count++;
pthread_mutex_unlock(&lock);
return 0;
}
6. 子スレッドの実行機能
template <typename T>
void* threadpool<T>::routine(void* arg)
{
threadpool<T>* pool=(threadpool<T>*) arg;
while(true)
{
/*
1.加锁,进入请求池获取任务
2.如果没有任务,则阻塞在条件变量中,解锁
3.如果有任务,取出任务则运行该任务
*/
pthread_mutex_lock(&pool->lock);
while(pool->task_queue->cur_count==0&&pool->close==false)
{
pthread_cond_wait(&pool->cond,&pool->lock);
}
if(pool->close==true)break;
task_queue_t<T*>* queue=pool->task_queue;
T* t=queue->queue[queue->head];
pool->task_queue->head=(pool->task_queue->head+1)%pool->task_queue->max_count;
pool->task_queue->cur_count-=1;//任务数量-1
pthread_mutex_unlock(&pool->lock);
t->process();
}
//退出线程
pool->started--;//活跃线程减1
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
return NULL;
}
7. デストラクター
template <typename T>
threadpool<T>::~threadpool()
{
/*
1.先请求池中的任务全部处理完
2.在退出所有的线程
3.释放线程池资源
*/
if(pool==nullptr){
return;
}
if(pthread_mutex_lock(&lock)<0){
//加锁失败
m_errno=2;
return;
}
if(close==true)
{
thread_pool_free();
return;
}
close=true;
if(pthread_cond_broadcast(&cond)<0||pthread_mutex_unlock(&lock)<0){
thread_pool_free();
m_errno=3;
return;
}
wait_all_done();
thread_pool_free();
return;
}
template <typename T>
int threadpool<T>::wait_all_done()
{
int ret=0;
if(pool==nullptr){
return 1;
}
for(int i=0;i<thrd_count;i++)
{
if(pthread_join(pool[i],NULL)!=0){
ret=1;
}
}
return ret;
}
template <typename T>
int threadpool<T>::thread_pool_free()
{
if(pool!=nullptr||started>0){
//还有活跃线程,不能还不能销毁资源
return -1;
}
delete[] pool;
pool=nullptr;
if(task_queue!=nullptr)
{
delete task_queue;
task_queue=nullptr;
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
started=0;
thrd_count=0;
}
スレッド プール リソースを解放する前に、子スレッドが終了するまで待つ必要があるのはなぜですか?
スレッド プール内のすべてのタスクを確実に完全に実行できるようにするため、スレッド プールに実行中のスレッドがまだある場合、メイン スレッドが突然終了した場合、一部のタスクが実行されないか、実行が不完全になります。
8. スレッドプールをテストする
次のコードは、スレッド プール内のスレッドがタスクを同時に実行できるかどうかをテストします。
struct task{
static int done ;
static pthread_mutex_t lock;
public:
void process(){
usleep(10000);
pthread_mutex_lock(&lock);
done++;
printf("%d:doing %d task\n",pthread_self(), done);
pthread_mutex_unlock(&lock);
}
};
int task::done = 0;
pthread_mutex_t task::lock;
int main()
{
threadpool<task>* pool=new threadpool<task>();
if (pool == NULL) {
printf("thread pool create error!\n");
return 1;
}
task* t=new task;
while (pool->push(t)==0) {
/// pthread_mutex_lock(&lock);
nums++;
// pthread_mutex_unlock(&lock);
t=new task;
}
printf("add %d tasks\n", nums);
pool->wait_all_done();
// printf("did %d tasks\n", done);
}
9. スレッドプール内のスレッド数の設定
スレッド プール内のスレッドの数は、CPU リソースとバランスを取る必要があります。
CPU は 1 つのスレッドしか実行できないため、スレッド内のスレッド数はできるだけ多くありませんが、スレッド プールを作成する目的は、CPU リソースを最大限に活用することです。
CPUリソースを再利用するにはどうすればよいですか?
一部のスレッドがブロックされて IO を待機している場合、その時点では CPU リソースは使用されませんが、この時点で別のスレッドに切り替えて CPU が操作を継続できるようにすることができます。
1.経験値
スレッド数を構成する前に、まずタスクのタイプがIO 集中型かCPU 集中型かを確認する必要があります。
IO集中型とは何ですか?
たとえば、ディスク上のデータが頻繁に読み取られる場合や、インターフェイスをネットワーク経由でリモートから呼び出す必要がある場合などです。
CPU 集中型とは何ですか?
例: 非常に複雑な呼び出し、多くのループ、または深い再帰呼び出しなど。
混合タイプ ( IO 集中型と CPU 集中型の両方を含む) の場合、スレッド数はどのように構成すればよいですか?
ハイブリッド タイプがIO 集中型で、CPU 集中型の実行時間がそれほど変わらない場合は、構成を改善するために分離できます。実行時間が違いすぎる場合、最適化の意味はほとんどありません。たとえば、IO 集中型の実行には 60 秒かかり、CPU 集中型の実行には 1 秒かかります。
2. 最適スレッド数アルゴリズム
上記で紹介した経験値に加えて、計算式も提供されます。
最適なスレッド数 = ((スレッド待機時間 + スレッド CPU 時間) / スレッド CPU 時間) * CPU 数
明らかに、スレッド待機時間の割合が高くなるほど、より多くのスレッドが必要になるためです。スレッド CPU 時間の割合が高くなるほど、必要なスレッドが少なくなります。