【操作系统-线程池】Pthread 线程池的设计及实现

15 篇文章 0 订阅
11 篇文章 1 订阅

Lab Week 15 实验报告

实验内容:设计实现一个线程池 (Thread Pool)

  • 使用 Pthread API 管理线程。
  • 利用回调函数作为线程的启动函数。
  • 采用 pthread 信号量解决线程池分配的互斥问题。
  • 讨论上述方案的技术可行性,尝试写一个设计报告。
  • 在一个源代码文件内实现编码、完成编译、运行和用例测试。

I.线程池

线程池的提出,主要是为了解决

(1)频繁地为某一任务创建和销毁线程所引起的系统资源耗费问题;

(2)无限制地为并发请求创建线程且系统对线程数量没有限制而引起的系统资源耗费问题;

线程池的主要思想是:在进程一开始启动时即创建一定数量空闲的线程,加入到线程池中等待工作。当任务传给线程池的时候,线程池会唤醒一个线程来执行这个任务,一旦线程完成任务,就会回到线程池中等待新的任务到来,如果线程池中没有空闲的线程,那么会等到有空闲线程为止。

线程池具有以下优点:

  1. 用现有线程服务请求比等待创建一个线程更快。
  2. 线程池限制了任何时候可用线程的数量。这对那些不能支持大量并发线程的系统非常重要。
  3. 将要执行任务从创建任务的机制中分离出来,允许我们采用不同策略运行任务。例如,任务可以被安排在某一个时间延迟后执行,或定期执行。

II.线程池设计

A. 线程池组成

(1)线程管理函数(ThreadPool):用于创建线程池、销毁线程池、添加新任务;

(2)任务队列(TaskQueue):用于管理任务的队列(FIFO),将新的任务加到队列尾部,即将执行的任务在队列头部;

(3)工作线程(Workers):线程池中的线程,在任务到来时分配给工作线程,在没有任务时等待;

注意:在任务队列的部分,需要保证一个任务只能被一个线程取走,所以需要引入pthread 信号量以解决线程池分配的互斥问题

image-20220525153358375

B. 设计思路:

(1)在主函数中调用线程池初始化函数,同时线程池中的线程执行线程函数,随后将任务添加到任务队列中;

(2)在线程函数中,每个线程对当前任务队列的状态进行判断,若不空,则取出任务执行,若为空则等待;

(3)待任务添加完且线程执行完所有任务后,对线程进行回收并销毁线程池及其他动态创建的堆栈和信号量;

(4)程序结束;

C.实现代码:

代码模块:

以下对线程池的实现根据不同功能相对应的模块进行说明:

任务结构和线程池结构

typedef struct
{
    void*(*func)(void*);
    void*args;
}threadpool_task;

struct threadpool
{
    threadpool_task* task_queue; // 任务队列
    sem_t unnamded_sem; //匿名信号量
    int front;
    int rear;
    int queue_size; //队列大小
    int thread_num;
    int count; //等待任务数
    pthread_t* ptid; //线程tid表
    int tasknum; //总的任务数
};

代码说明:

这部分定义了线程需要完成的任务和线程池的数据结构;

在任务结构中,定义了该任务对应的回调函数和相应的参数;

在线程池结构中,定义了任务队列和相应的控制变量、与任务相关的变量、解决线程池分配的互斥问题的匿名信号量;

线程池初始化函数

threadpool* threadpool_create(int thread_num, int queue_size, int task_num){
  /*
  	param: thread_num 线程数
  	param: queue_size 队列大小
  	param: task_num 任务数
  
  */
    int ret, i;
    if(thread_num > MAX_THREADS || queue_size > MAX_QUEUE){
        return NULL;
    }

    threadpool* pool;
    if((pool = (threadpool*)malloc(sizeof(threadpool))) == NULL){
        perror("threadpool:malloc()");
    }

    //Initialize thread pool
    pool->front = pool->rear = 0;
    pool->queue_size = queue_size;
    pool->thread_num = 0; 
    pool->tasknum = task_num;
  
    // Allocate thread and task queue
    pool->task_queue = (threadpool_task*)malloc(sizeof(threadpool_task)*queue_size);
    pool->ptid = (pthread_t*) malloc(sizeof(pthread_t)*thread_num);

    //Initialize unnamed semaphore
    ret = sem_init(&pool->unnamded_sem, 0, 1);
    if(ret == -1) {
        perror("sem_init()");
    }
    
    //Create threads
    for(i = 0; i<thread_num; i++){
        ret = pthread_create(&(pool->ptid[i]), NULL, &threadpool_exec, (void*)pool);
        if(ret != 0) {
            perror("producer pthread_create()");
            break;
        }
        pool->thread_num++; // 进入线程池的线程数加1
    }
     
    return pool;
}

代码说明:

这部分对线程池进行初始化,其中包括:

  • 用malloc进行动态分配:线程池,任务队列、线程号表;

  • 对匿名信号量进行初始化,sem_init(&pool->unnamded_sem, 0, 1) ,将信号量初始化为1,并限制在当前进程使用;

  • pthread_create(&(pool->ptid[i]), NULL, &threadpool_exec, (void*)pool) 创建线程,并调用函数threadpool_exec() ,传递的参数为线程池指针;

任务添加函数

void threadpool_add(threadpool * pool, void*(*func)(void*), void*args){
    /*
  	param: pool 线程池
  	param: func 任务函数
  	param: args 函数参数
  
  */
    while((pool->rear + 1) % pool->queue_size == pool->front){ // 当任务队列满的时候
        printf("Task queue is full, task No.%d waitting for handling\n", *(int*)args);
        sleep(1);
    }

    pool->task_queue[pool->rear].func = func;
    pool->task_queue[pool->rear].args = args;
    pool->rear = (pool->rear + 1) % pool->queue_size;
    pool->count += 1; //任务队列中的任务数加1

}

代码说明:

这部分主要负责向任务队列中添加函数,通过该函数传进来的任务函数和相应的参数,对线程池中的任务队列添加任务,并对控制变量进行修改;

线程执行函数

static void *threadpool_exec(void *arg){
    threadpool* pool = (threadpool*)arg;
    threadpool_task task;
    while(1){
        while(pool->front == pool->rear){ //当队列为空时
            if(!pool->tasknum){ // 如果所有任务都已经处理完毕,则退出线程
                printf("Thread %ld exits\n", gettid());
                pthread_exit(NULL);
            }
            printf("Thread %ld is waitting for new task\n", gettid()); //否则打印出线程等待的信息
            sleep(1);
        }
        if(pool->tasknum == 0){
            break;
        }
        //利用信号量保证一个线程只能处理一个任务
        sem_wait(&pool->unnamded_sem); //获得信号量

        //线程获得任务
        task.func = pool->task_queue[pool->front].func;
        task.args = pool->task_queue[pool->front].args;
        pool->front = (pool->front + 1) % pool->queue_size; 
        pool->count -= 1; //任务队列中的任务数减1
        pool->tasknum -= 1; // 总任务数减1
        sem_post(&pool->unnamded_sem); //释放信号量

        // Go to Work!!
        (*(task.func))(task.args);
    }
}

代码说明:

这部分代码为线程调用的函数,其中包括对线程池分配的互斥问题、线程获取任务、等待任务的处理;

  • 当队列为空时,如果所有任务执行完毕,则让线程退出,反之则打印出等待信息

  • 为了让一个线程只处理一个任务,这里用了Pthread信号量来解决线程池分配的互斥问题,通过 sem_wait(&pool->unnamded_sem)来获取信号量,随后从任务队列中获取任务,修改控制变量后,执行sem_post(&pool->unnamded_sem) 释放信号量,之后,再利用

    (*(task.func))(task.args)来调用任务函数;

任务函数

void* taskfun(void* arg){
    int* temp = (int*) arg;
    int task_sn = *temp;
    printf("Thread %ld is working on task No.%d\n", gettid(), task_sn);
    printf("---- Thread %ld working last for 2s -----\n", gettid());
    sleep(2);
    printf("\t\t  Task No.%d finished\n", task_sn);
    return 0;
}

代码说明:

这部分为任务具体需要实现的内容,在这里,打印出线程号和它所处理的任务的序列号(以函数形参方式传入),随后sleep 2s,这样能够模拟线程在调用该任务函数时处理的过程,最后打印出结束标志;

线程池销毁函数

void threadpool_destroy(threadpool* pool){
    free(pool->task_queue);
    free(pool->ptid);
    sem_destroy(&pool->unnamded_sem);
    free(pool);
}

代码说明:

这部分对动态创建的线程池、线程号表、线程池还有匿名信号量进行销毁。

主函数

int main(){
    int i, ret;
    int thread_num, queue_size, task_num;
    thread_num = MAX_THREADS;
    queue_size = MAX_QUEUE;
    task_num = MAX_TASKS;
    printf("------Thread pool parameters-------\n");
    printf("Number of thread: %d, Size of task queue: %d, Number of task: %d\n", thread_num, queue_size, task_num);
    sleep(1);
    
    threadpool* pool = threadpool_create(thread_num, queue_size, task_num);
    if(pool == NULL){
        perror("threadpool_create()");
    }
    sleep(2);  //进入休眠,同时测试线程是否会等待任务队列添加任务

    for(i=0; i < task_num; ++i){
        printf("Adding task No.%d\n",i);
        threadpool_add(pool, &taskfun, &i); //添加任务
        sleep(1);
    }


    for(int i=0;i<thread_num;i++){
        pthread_join(pool->ptid[i], NULL);
    }
    printf("Tasks Completed, starting destroy\n");
    threadpool_destroy(pool);
    printf("\n\tExit Successfully\n");
    exit(EXIT_SUCCESS);
}

代码说明:

这部分为代码的主函数部分,在一开始调用threadpool_create(thread_num, queue_size, task_num)对线程池进行初始化,创建相应大小的任务队列、线程数和任务数,此后休眠2s,这里为了测试线程是否会等待任务队列添加任务;

然后通过循环调用 threadpool_add(pool, &taskfun, &i)来向任务队列中添加任务,每隔1s添加一次任务;

完整代码:
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/syscall.h>
#include<semaphore.h>
#include<unistd.h>

#define gettid() syscall(__NR_gettid)
#define MAX_THREADS 10
#define MAX_QUEUE 12
#define MAX_TASKS 20
typedef struct threadpool threadpool;
threadpool * threadpool_create(int thread_num, int queue_size, int task_num);
void threadpool_add(threadpool * pool, void*(*func)(void*), void*args);
static void *threadpool_exec(void *threadpool);


typedef struct
{
    void*(*func)(void*);
    void*args;
}threadpool_task;

struct threadpool
{
    threadpool_task* task_queue;
    sem_t unnamded_sem;
    int front;
    int rear;
    int queue_size;
    int thread_num;
    int count; //等待任务数
    pthread_t* ptid;
    int tasknum; //总的任务数
};

threadpool* threadpool_create(int thread_num, int queue_size, int task_num){
    int ret, i;
    if(thread_num > MAX_THREADS || queue_size > MAX_QUEUE){
        return NULL;
    }

    threadpool* pool;
    if((pool = (threadpool*)malloc(sizeof(threadpool))) == NULL){
        perror("threadpool:malloc()");
    }

    //Initialize thread pool
    pool->front = pool->rear = 0;
    pool->queue_size = queue_size;
    pool->thread_num = 0;
    pool->tasknum = task_num;
    // Allocate thread and task queue
    pool->task_queue = (threadpool_task*)malloc(sizeof(threadpool_task)*queue_size);
    pool->ptid = (pthread_t*) malloc(sizeof(pthread_t)*thread_num);

    //Initialize unnamed semaphore
    ret = sem_init(&pool->unnamded_sem, 0, 1);
    if(ret == -1) {
        perror("sem_init()");
    }
    
    //Create threads
    for(i = 0; i<thread_num; i++){
        ret = pthread_create(&(pool->ptid[i]), NULL, &threadpool_exec, (void*)pool);
        if(ret != 0) {
            perror("producer pthread_create()");
            break;
        }
        pool->thread_num++;
    }
     
    return pool;
}

static void *threadpool_exec(void *arg){
    threadpool* pool = (threadpool*)arg;
    threadpool_task task;
    while(1){
        // sem_wait(&pool->unnamded_sem);
        while(pool->front == pool->rear){
            if(!pool->tasknum){
                printf("Thread %ld exits\n", gettid());
                pthread_exit(NULL);
            }
            printf("Thread %ld is waitting for new task\n", gettid());
            sleep(1);
        }
        if(pool->tasknum == 0){
            break;
        }
        //利用信号量保证一个线程只能处理一个任务
        sem_wait(&pool->unnamded_sem);

        //线程获得任务
        task.func = pool->task_queue[pool->front].func;
        task.args = pool->task_queue[pool->front].args;
        pool->front = (pool->front + 1) % pool->queue_size;
        pool->count -= 1;
        pool->tasknum -= 1;
        sem_post(&pool->unnamded_sem);

        // Go to Work!!
        (*(task.func))(task.args);
    }
}

void* taskfun(void* arg){
    int* temp = (int*) arg;
    int task_sn = *temp;
    printf("Thread %ld is working on task No.%d\n", gettid(), task_sn);
    printf("---- Thread %ld working last for 2s -----\n", gettid());
    sleep(2);
    printf("\t\t  Task No.%d finished\n", task_sn);
    return 0;
}

void threadpool_add(threadpool * pool, void*(*func)(void*), void*args){
    while((pool->rear + 1) % pool->queue_size == pool->front){ // 当任务队列满的时候
        printf("Task queue is full, task No.%d waitting for handling\n", *(int*)args);
        sleep(1);
    }

    pool->task_queue[pool->rear].func = func;
    pool->task_queue[pool->rear].args = args;
    pool->rear = (pool->rear + 1) % pool->queue_size;
    pool->count += 1;

}


void threadpool_destroy(threadpool* pool){
    free(pool->task_queue);
    free(pool->ptid);
    sem_destroy(&pool->unnamded_sem);
}

int main(){
    int i, ret;
    int thread_num, queue_size, task_num;
    thread_num = MAX_THREADS;
    queue_size = MAX_QUEUE;
    task_num = MAX_TASKS;
    printf("------Thread pool parameters-------\n");
    printf("Number of thread: %d, Size of task queue: %d, Number of task: %d\n", thread_num, queue_size, task_num);
    sleep(1);
    
    threadpool* pool = threadpool_create(thread_num, queue_size, task_num);
    if(pool == NULL){
        perror("threadpool_create()");
    }
    sleep(2);  //进入休眠,同时测试线程是否会等待任务队列添加任务

    for(i=0; i < task_num; ++i){
        printf("Adding task No.%d\n",i);
        threadpool_add(pool, &taskfun, &i); //添加任务
        sleep(1);
    }


    for(int i=0;i<thread_num;i++){
        pthread_join(pool->ptid[i], NULL);
    }
    printf("Tasks Completed, starting destroy\n");
    threadpool_destroy(pool);
    printf("\n\tExit Successfully\n");
    exit(EXIT_SUCCESS);
}

D.实验结果:

初始阶段:

在线程池中初始化10个线程,任务队列的大小为12,需要完成的总任务数为20;

image-20220525130537822

在一开始,由于是先对线程池进行初始化,在sleep 2s之后才向任务队列中添加任务,红框的部分为线程池中的线程在等待任务时打印出来的信息,如:Thread 28389 is waitting for new task,表示线程号为28389的线程正在线程池中等待系统分配给它任务;

工作阶段:

此时开始向任务队列中添加任务,任务以序列号标识,No.0表示序列号为0的任务:

image-20220525130630138

在添加完任务No.0之后,线程号为28394的线程首先接手这个任务,完成这个任务需要费时2s,由于任务1s才添加一个,所以在这期间线程需要等待,随后任务No.1进入任务队列,线程号为28392的线程接手这个任务,之后任务No.0被线程28394完成,它又重新回到线程池中等待任务,随后任务No.2进入任务队列,线程号为28397的线程接手这个任务,任务No.1被线程28392完成;

image-20220525130706962

注意到,在添加完任务No.6之后,线程号为28394的线程接手这个任务,该线程在之前曾接手过任务No.0;任务No.5也相继完成。

在添加完No.7的任务之后,线程号为28398的线程接手这个任务;

image-20220525130735324

在添加完任务No.17之后,线程号为28398的线程接手这个任务,该线程在之前曾接手过任务No.7;

线程28398执行完任务No.19之后,所有任务执行完毕,所有线程开始退出,最后调用销毁函数,线程池销毁,程序结束。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 实现线程池的方法有很多,但是最常见的方法是使用队列来维护任务。每个线程都在队列中等待任务,当有新任务到达时,就从队列中取出一个任务并执行。这样,每个线程都可以在并行执行任务,而不需要创建新的线程。 在C语言实现线程池的代码如下: ``` #include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 5 void *print_hello(void *threadid) { long tid; tid = (long)threadid; printf("Hello World! It's me, thread #%ld!\n", tid); pthread_exit(NULL); } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc; long t; for (t = 0; t < NUM_THREADS; t++) { printf("In main: creating thread %ld\n", t); rc = pthread_create(&threads[t], NULL, print_hello, (void *)t); if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(NULL); } ``` ### 回答2: 要实现线程池,首先需要先了解线程池的基本概念和原理。 线程池是一种用来管理和复用线程的技术,它能够维护一个线程队列,按需创建和销毁线程,并将任务分配给这些线程来执行。使用线程池可以提高程序的性能,减少线程创建和销毁的开销。 在C语言中,可以使用多线程的库来实现线程池,比如pthread库。下面是一个简单的用C语言实现线程池的步骤: 1. 定义线程池结构体:创建一个结构体来保存线程池的相关信息,如线程池的大小、任务队列、互斥锁、条件变量等。 2. 初始化线程池:在初始化函数中,需要对线程池中的各个成员进行初始化,如创建指定数量的线程、初始化互斥锁和条件变量等。 3. 定义任务函数:线程池的任务函数用于处理任务队列中的任务,根据具体需求来定义任务的执行逻辑。 4. 添加任务到线程池:当有新的任务时,将任务添加到任务队列中,并通过条件变量来通知线程池中的线程有新任务可执行。 5. 线程池中的线程获取任务并执行:在线程中循环检查任务队列,当有任务时,线程从任务队列中获取任务并执行。 6. 销毁线程池:在停止使用线程池时,要销毁线程池中的资源,包括线程的回收、互斥锁和条件变量的销毁等。 通过以上步骤,就可以在C语言实现一个简单的线程池。具体实现中还需要考虑线程同步、任务队列的管理等问题,以确保线程池的稳定性和性能。 ### 回答3: 线程池是用来管理和复用线程的一种机制,可以更有效地使用系统资源和提高应用程序的性能。下面是使用C语言实现线程池的一般步骤: 1. 定义一个线程池的结构体,包含线程池的状态、大小、最大线程数、工作任务队列等信息。 ```c typedef struct { pthread_t *threads; // 线程数组 int thread_count; // 线程数 int max_threads; // 最大线程数 int pool_size; // 线程池大小 int shutdown; // 关闭标志 pthread_mutex_t mutex; // 互斥锁 pthread_cond_t notify; // 条件变量 Task *task_queue; // 任务队列 } ThreadPool; ``` 2. 初始化线程池,创建指定数量的线程。 ```c ThreadPool* thread_pool_init(int pool_size) { ThreadPool *pool = malloc(sizeof(ThreadPool)); pool->threads = malloc(pool_size * sizeof(pthread_t)); pool->thread_count = pool_size; pool->max_threads = pool_size; pool->pool_size = 0; pool->shutdown = 0; // 初始化互斥锁和条件变量 pthread_mutex_init(&(pool->mutex), NULL); pthread_cond_init(&(pool->notify), NULL); // 创建线程 for (int i = 0; i < pool_size; i++) { pthread_create(&(pool->threads[i]), NULL, thread_worker, (void*)pool); } return pool; } ``` 3. 定义线程工作函数,不断从任务队列中取出任务执行。 ```c void* thread_worker(void *arg) { ThreadPool *pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&(pool->mutex)); // 线程池关闭,退出线程 while (pool->pool_size == 0 && !pool->shutdown) { pthread_cond_wait(&(pool->notify), &(pool->mutex)); } if (pool->shutdown) { pthread_mutex_unlock(&(pool->mutex)); pthread_exit(NULL); } // 从任务队列中取出任务执行 Task *task = pool->task_queue; pool->task_queue = pool->task_queue->next; pool->pool_size--; pthread_mutex_unlock(&(pool->mutex)); task->func(task->arg); free(task); } pthread_exit(NULL); } ``` 4. 定义任务结构体,包含任务函数指针和参数。 ```c typedef struct Task { void (*func)(void*); void *arg; struct Task *next; } Task; ``` 5. 向线程池中添加任务。 ```c void thread_pool_add_task(ThreadPool *pool, void (*func)(void*), void *arg) { Task *task = malloc(sizeof(Task)); task->func = func; task->arg = arg; task->next = NULL; pthread_mutex_lock(&(pool->mutex)); if (pool->task_queue == NULL) { pool->task_queue = task; } else { Task *cur = pool->task_queue; while (cur->next != NULL) { cur = cur->next; } cur->next = task; } pool->pool_size++; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_signal(&(pool->notify)); } ``` 6. 关闭线程池。 ```c void thread_pool_shutdown(ThreadPool *pool) { if (pool == NULL) { return; } pthread_mutex_lock(&(pool->mutex)); pool->shutdown = 1; pthread_mutex_unlock(&(pool->mutex)); pthread_cond_broadcast(&(pool->notify)); // 等待线程退出 for (int i = 0; i < pool->thread_count; i++) { pthread_join(pool->threads[i], NULL); } // 释放资源 free(pool->threads); while (pool->task_queue != NULL) { Task *next = pool->task_queue->next; free(pool->task_queue); pool->task_queue = next; } pthread_mutex_destroy(&(pool->mutex)); pthread_cond_destroy(&(pool->notify)); free(pool); } ``` 以上是一个简单的线程池实现,通过初始化线程池、添加任务、关闭线程池等操作,可以有效地管理和复用线程,提高应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值