(P41)一個簡單的線程池實現

1.線程池說明

  • 線程池擁有若干個線程
  • 用於執行大量相對短暫的任務
    由於線程個數小於任務的數量,因而任務需要進入隊列中進行等待,在短時間可以得到一個線程對任務進行處理
  • 線程的個數與任務的類型有關
    (1)計算密集型任務: 執行任務時很少被阻塞,線程個數=CPU個數(併發數是確定的),線程數過多,會導致線程上下文切換,降低效率
    (2)I/O密集型任務: 執行任務時,可能會被I/O中斷,即線程被掛起,所以線程個數>CPU個數,若線程少了,剛好又都被掛起,當有新任務來了,無法處理任務了
  • 當任務增加的時候能夠動態的增加線程池中線程的數量直到達到一個閾值
    當任務執行完畢的時候,能夠動態的銷燬線程池中的線程
  • 該線程池的實現本質上也是生產者與消費者模型的應用。
    生產者線程向任務隊列中添加任務,一旦隊列有任務到來,如果有等待線程就喚醒來執行任務,如果沒有等待線程並且線程數沒有達到閾值,就創建新線程來執行任務。

2.線程池實現

  • 測試驅動開發,先將數據結構寫好,再寫接口,然後make一下看是否報錯,最後再寫實現
// 任務結構體,將任務放入隊列由線程池中的線程來執行
typedef struct task
{
    void *(*run)(void *arg);    // 任務回調函數
    void *arg;                  // 回調函數參數
    struct task* next;//任務組織成鏈表的方式來保存
} task_t;

// 線程池結構體
typedef struct threadpool
{
    condition_t ready;  // 任務準備就緒或者線程池銷燬通知
    task_t *first;      // 任務隊列頭指針
    task_t *last;       // 任務隊列尾指針
    int counter;        // 線程池當前線程數
    int idle;           // 線程池中當前正在等待任務的線程數,處於空閒狀態的線程個數
    int max_threads;    // 線程池中最大允許的線程數,線程池的閾值
    int quit;           // 銷燬線程池的時候置1
} threadpool_t;

// 初始化線程池
void threadpool_init(threadpool_t *pool, int threads);

// 在線程池中添加任務
void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);

// 銷燬線程池
void threadpool_destroy(threadpool_t *pool);
  • eg:線程池的實現
    NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\threadpool.h
    NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\threadpool.c
========================NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\threadpool.h========
//
// Created by wangji on 19-8-15.
//

// p41 線程池

#ifndef NETWORKPROGRAMMING_THREADPOOL_H
#define NETWORKPROGRAMMING_THREADPOOL_H

#include "condition.h"

// 任務結構體,將任務放入隊列由線程池中的線程來執行
typedef struct task
{
    void *(*run)(void *arg);    // 任務回調函數
    void *arg;                  // 回調函數參數
    struct task* next;//任務組織成鏈表的方式來保存
} task_t;

// 線程池結構體
typedef struct threadpool
{
    condition_t ready;  // 任務準備就緒或者線程池銷燬通知
    task_t *first;      // 任務隊列頭指針
    task_t *last;       // 任務隊列尾指針
    int counter;        // 線程池當前線程數
    int idle;           // 線程池中當前正在等待任務的線程數,處於空閒狀態的線程個數
    int max_threads;    // 線程池中最大允許的線程數,線程池的閾值
    int quit;           // 銷燬線程池的時候置1
} threadpool_t;

// 初始化線程池
void threadpool_init(threadpool_t *pool, int threads);

// 在線程池中添加任務
void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);

// 銷燬線程池
void threadpool_destroy(threadpool_t *pool);


#endif //NETWORKPROGRAMMING_THREADPOOL_H

======NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\threadpool.c==========
//
// Created by wangji on 19-8-15.
//

// p41 線程池

#include "threadpool.h"
#include <pthread.h>
#include <memory>
// #include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

//消費者
void *thread_routine (void *arg)
{
    struct timespec abstime;
    int timeout;//超時標記
    printf("thread 0x%x is starting\n", (int)pthread_self());//%x16進制打印
    threadpool_t *pool = (threadpool_t*)arg;
    while (1)
    {
        timeout = 0;
        condition_lock(&pool->ready);
        pool->idle++;
        // 等待隊列有任務到來或者線程池銷燬通知
        while (pool->first == NULL && !pool->quit)
        {
            printf("thread 0x%x is waiting\n", (int)pthread_self());
            //condition_wait(&pool->ready);

            //下面是帶超時的contion_timewait
            clock_gettime(CLOCK_REALTIME, &abstime);
            abstime.tv_sec += 2; // 超時設置2秒
            int state = condition_timewait(&pool->ready, &abstime);
            if (state == ETIMEDOUT)
            {
                printf("thread 0x%x is wait time out\n", (int)pthread_self());
                timeout = 1;
                break;//超時了就break
            }
        }
        // 等待到條件,處於工作狀態
        pool->idle--;
        if (pool->first != NULL)//有任務了
        {
            // 從隊頭取出任務
            task_t *t = pool->first;
            pool->first = t->next;//取出任務,隊頭髮生改變

            // 執行任務需要一定的時間,所以需要先解鎖,以便生產者線程
            // 能夠往隊列中添加任務,其他消費者線程能夠進入等待任務
            condition_unlock(&pool->ready);
            t->run(t->arg);
            free(t);//任務執行完,則銷燬
            condition_lock(&pool->ready);
        }
        // 如果等待到銷燬線程池通知,且任務都執行完畢
        if (pool->quit && pool->first == NULL)
        {
            pool->counter--;
            if (pool->counter == 0)//目的是銷燬函數中的處於執行任務狀態中的線程能夠結束
            {
                condition_signal(&pool->ready);
            }
            condition_unlock(&pool->ready);// 跳出循環之前記得解鎖
            break;
        }

        //超時break
        if (timeout && pool->first == NULL)
        {
            pool->counter--;
            condition_unlock(&pool->ready);// 跳出循環之前記得解鎖
            break;
        }

        condition_unlock(&pool->ready);
    }

    printf("thread 0x%x is exiting \n", (int)pthread_self());//表示線程銷燬了

    return NULL;
}

// 初始化線程池
void threadpool_init(threadpool_t *pool, int threads)
{
   // pool = (threadpool_t*)malloc(sizeof(pool));
   //對線程池中的各個字段初始化
    condition_init(&(pool->ready));
    pool->first = NULL;
    pool->last = NULL;
    pool->counter = 0;
    pool->idle = 0;
    pool->max_threads = threads;
    pool->quit = 0;
}

//生產者
// 在線程池中添加任務
void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg)
{
    void *(*run)(void *arg);    // 任務回調函數
    void *arg;                  // 回調函數參數
    struct task* next;//任務組織成鏈表的方式來保存

    // 生成新任務
    task_t* newtask = (task_t *)malloc(sizeof(task_t));
    //cout << (char *)pool->first->arg << endl;
    newtask->run = run;
    newtask->arg = arg;
    newtask->next = NULL;//往隊列尾部添加


    condition_lock(&pool->ready);
    // 將任務添加到隊列中,單鏈表的應用
    if (pool->first == NULL)//第一次添加任務
    {
        pool->first = newtask;
    }
    else
    {
        //後面添加的就將其添加到尾部
        pool->last->next = newtask;
    }
    //添加任務後,尾指針要發生改變
    pool->last = newtask;

    // 如果有等待任務的線程,則喚醒其中一個
    if (pool->idle > 0)//當前等待的線程數
    {
        condition_signal(&pool->ready);
    }
    else if (pool->counter < pool->max_threads)
    {
        // 沒有等待線程,並且當前線程數不超過最大線程數,則創建一個新線程
        pthread_t tid;
        pthread_create(&tid, NULL, thread_routine, pool);
        ++pool->counter;
    }
    condition_unlock(&pool->ready);
}

// 銷燬線程池
void threadpool_destroy(threadpool_t *pool)
{
    if (pool->quit)
    {
        return;
    }
    condition_lock(&pool->ready);
    pool->quit = 1;//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);
    //free(pool);
}
  • eg:線程池的條件變量和互斥量的封裝
    NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\condition.h
    NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\condition.c
===============NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\condition.h==================
//
// Created by wangji on 19-8-15.
//

// p41 線程池

#ifndef NETWORKPROGRAMMING_CONDITION_H
#define NETWORKPROGRAMMING_CONDITION_H

#include <pthread.h>

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);//對互斥鎖進行解鎖
int condition_wait(condition_t *cond);//等待條件
int condition_timewait(condition_t *cond, const struct timespec *abstime);//超時登臺
int condition_signal(condition_t *cond);
int condition_broadcast(condition_t *cond);
int condition_destroy(condition_t *cond);


#endif //NETWORKPROGRAMMING_CONDITION_H
===================NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\condition.c====================
//
// Created by wangji on 19-8-15.
//

#include "condition.h"
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

int condition_init(condition_t *cond)
{
    int state;
    state = pthread_cond_init(&(cond->pcond), NULL);
    if (state == 0)
    {
        return state;
    }
    state = pthread_mutex_init(&(cond->pmutex), NULL);
    if (state == 0)
    {
        return state;
    }
    return 0;
}

int condition_lock(condition_t *cond)
{
    //cout << pthread_mutex_lock(&(cond->pmutex)) << endl;
    if (pthread_mutex_lock(&(cond->pmutex)) == 0)
    {
        printf("pthread_mutex_lock error\n");
        exit(EXIT_FAILURE);
    }
    return 1;
}

int condition_unlock(condition_t *cond)
{
    pthread_mutex_unlock(&(cond->pmutex));
}
int condition_wait(condition_t *cond)
{
    return pthread_cond_wait(&(cond->pcond), &(cond->pmutex));
}

int condition_timewait(condition_t *cond, const struct 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 state;
    state = pthread_cond_destroy(&(cond->pcond));
    if (state == 0)
    {
        return state;
    }
    state = pthread_mutex_destroy(&(cond->pmutex));
    if (state == 0)
    {
        return state;
    }
    return 0;
}
  • NetworkProgramming-master (1)\NetworkProgramming-master\P41threadpool\test.c
//
// Created by wangji on 19-8-15.
//

// p41 線程池

#include "condition.h"
#include "threadpool.h"
#include <iostream>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

void *run (void *arg)
{
    printf("threadpool 0x%x working task %d\n", (int)pthread_self(), *(int*)(arg));
    sleep(1);
    free(arg);
    return NULL;
}

int main(void)
{
    // 結構體指針需要初始化 不曉得爲啥報錯
    threadpool_t pool;
    threadpool_init(&pool, 3);//初始化3個線程

    int i;
    for (i = 0; i < 10; ++i)//向線程池,添加10個任務
    {
        int *a = (int*)malloc(sizeof(int));
        *a = i;//傳遞動態內存,不會使得內部的某個指針指向i變量,因爲for循環後i發生了改變,不要寫成:threadpool_add_task(&pool, run, &i);
        threadpool_add_task(&pool, run, a);
    }
   // sleep(15);
    threadpool_destroy(&pool);//調用threadpool_destroy的主線程也要得到通知
    return 0;
}
  • 測試結果:
    在這裏插入圖片描述
    若將下面的代碼屏蔽,程序則無法銷燬
if (pool->counter == 0)//目的是銷燬函數中的處於執行任務狀態中的線程能夠結束
{
    condition_signal(&pool->ready);
}

在這裏插入圖片描述

  • Makefile
.PHONY:clean
CC=gcc
CFLAGS=-Wall -g
ALL=main
OBJS=threadpool.o main.o condition.o
all:$(ALL)
%.o:%.c
	$(cc) $(CFLAGS) -c $< -o $@
main:$(OBJS)
	$(cc) $(CFLAGS) $^ -o $@ -lpthread -lrt
clean:
	rm -f $(ALL) *.o
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章