同步互斥线程池

线程池------设计报告

背景:

  我们已经了解了多线程的应用可以提高计算机的效率,同样,我们也知道线程的创建有一个不小的开销,如果涉及到多线程的应用实例中所需线程较多的情况,多线程创建的开销会拖累整个计算机的效率,由此引申出了线程池的概念。

线程池的介绍与设计思想

  线程池的简单理解就是在执行任务之前,先创建一定数量的线程,这些线程需要有一个管理员来进行调度和管理。我们只需要将任务提交给管理员,管理员就会自行调度线程资源来完成任务。
  那么管理员是怎么是实现的呢?在这里的实现方法是:创建一个任务队列(FIFO),当接收到新的任务时,就把任务添加在队列的尾部。线程池时刻关注着队列的变化:如果队列非空,且线程池中有线程可用的话,线程池中的某一个线程就会取走该任务并执行,注意这里需要保证一个任务只能被一个线程取走;当任务处理完后,线程就继续去队列中取任务;如果任务队列为空,线程则在线程池中休眠,直到有新的任务加入任务队列。
在这里插入图片描述

到这里,我们可以发现:我们将线程的创建工作放在最开始的时候,也就是还没有任务加入任务队列的时候。当任务队列非空时,线程立即取走任务并执行,计算机的任务处理效率就大大提升了。

主要技术介绍

互斥锁

互斥锁概念的引入是为了保证共享数据操作的完整性。每个对象都对应于一个可称为"互斥锁"的标记,这个标记用于保证任意时刻,只能有一个线程访问该对象。

函数函数说明
int pthread_mutexattr_init(pthread_mutexattr_t *mattr);使用该函数可以将与互斥锁对象相关联的属性初始化为其缺省值。在执行过程中,线程系统会为每个属性对象分配存储空间
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)可用来取消分配用于维护pthread_mutexattr_init()所创建的属性对象的存储空间
int pthread_mutex_lock(pthread_mutex_t *mutex)线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止
int pthread_mutex_unlock(pthread_mutex_t *mutex)解除锁定 mutex 所指向的互斥锁。
条件变量

条件变量是线程使用的一种同步机制。条件变量给多个线程提供了会合的场所。条件变量和互斥量一起用的时候,允许线程以无竞争的方式等待特定的条件发生。条件是受互斥量保护的。线程在改变条件状态时必须首先对互斥量加锁。

函数函数说明
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);对条件变量进行初始化
int pthread_cond_destroy(pthread_cond_t *cond);对条件变量进行销毁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);当线程调用pthread_cond_wait()函数时,会将调用线程放到等待条件的线程列表上,并原子的对互斥量解锁(这样就不会死锁)。当pthread_cond_wait()返回时,互斥量再次锁住。
int pthread_cond_signal(pthread_cond_t * cond);pthread_cond_signal函数至少能唤醒一个等待该条件的线程。
int pthread_cond_broadcast(pthread_cond_t * cond);pthread_cond_broadcast函数能唤醒等待该条件的所有线程。

关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于该指针的,即不存在其它进行修改操作的途径;这样有利于编译器进行更好的代码优化,生成更有效率的汇编代码。
signal和brodcast 这两个函数都可以给条件等待线程发信号,不过需要注意的是,一定要在改变条件状态以后再给线程发信号。

assert(断言)

assert() 的用法像是一种"契约式编程",在我的理解中,其表达的意思就是,程序在我的假设条件下,能够正常良好的运作,其实就相当于一个 if 语句:

if(假设成立)
{
     程序正常运行;
}
else
{
      报错&&终止程序!(避免由程序运行引起更大的错误)  
}

assert定义在asset.h中,其作用是如果它的条件返回错误,则终止程序执行。
void assert( int expression )

assert 的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。

代码解析

在这里插入图片描述

这里值得注意的是:由于我们需要限制所创建得线程池中的线程的数量,所以我们采用线程数组的方式来实现线程池。任务队列亦是如此,head指向任务队列的队头,tail指向任务队列的队尾。由于任务队列是一个循环队列,所以当指针走到数组的尾部的时候需要折返回数组的头部,这在实现中并不困难。


任务的定义:
在这里插入图片描述

function指针指向任务需要执行的函数,argument表示函数执行所需的参数。


  由于线程池的实现是为完成任务服务的,又因为线程池实现时并不需要考虑太多的任务方面的东西,所以我们直接将线程池的实现写成一个API,当有实例需要用到线程池时直接调用即可。

API对外的接口有三个:

  • 线程创建函数:threadpool_t *threadpool_create(int thread_count, int queue_size,);
  • 任务添加函数:int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg);
  • 线程销毁函数:int threadpool_destroy(threadpool_t *pool);

  前面提到了我们在调用线程池这个API时,我们不需要考虑线程池具体是怎么实现的,管理员是怎么进行线程调度的。我们要做的只是在创建线程池之后往任务队列中添加任务,管理员就会自行调度线程来完成任务。当然,在使用完线程池之后我们需要将线程池销毁掉,回收内存资源。


函数函数说明
threadpool_t *threadpool_create(int thread_count, int queue_size,);创建线程池。thread_count指定线程池中线程的总数;queue_size指定任务队列的长度 ;返回一个线程池
int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg);添加需要执行的任务。pool对应已经创建好的线程池;routine指定任务所要执行的函数,arg对应函数的参数; 返回添加情况
int threadpool_destroy(threadpool_t *pool);销毁存在线程池。pool对应要被销毁的线程池;

源码细节解释

源代码一共包含五个函数:

  • threadpool_t *threadpool_create(int thread_count, int queue_size) :用于创建线程池
  • int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument) :向任务队列中添加任务
  • int threadpool_destroy(threadpool_t *pool) :销毁线程池
  • int threadpool_free(threadpool_t *pool) :释放线程池的内存空间
  • static void *threadpool_thread(void *threadpool) :线程所运行的函数,在此函数中实现线程的休眠与任务的分配。
threadpool_t *threadpool_create(int thread_count, int queue_size)

在这里插入图片描述
在这里插入图片描述

int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument)

在这里插入图片描述
在这里插入图片描述
获得互斥锁后,记得释放互斥锁。

int threadpool_destroy(threadpool_t *pool)

在这里插入图片描述
在这里插入图片描述

int threadpool_free(threadpool_t *pool)

在这里插入图片描述

static void *threadpool_thread(void *threadpool)

在这里插入图片描述
在这里插入图片描述

用例测试

测试程序如下:

#define THREAD 32
#define QUEUE  256

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include "Labweek13_Threadpool.h"

int tasks = 0, done = 0;
pthread_mutex_t lock;

void worker(void *arg) {
    
    pthread_mutex_lock(&lock);
    done++;
    pthread_mutex_unlock(&lock);
}

int main(int argc, char **argv)
{
    threadpool_t *pool;

    pthread_mutex_init(&lock, NULL);

    assert((pool = threadpool_create(THREAD, QUEUE)) != NULL);
    fprintf(stderr, "Pool started with %d threads and "
            "queue size of %d\n", THREAD, QUEUE);

    while(threadpool_add(pool, &worker, NULL) == 0) {
        pthread_mutex_lock(&lock);
        tasks++;
        pthread_mutex_unlock(&lock);
    }
    sleep(5);
    fprintf(stderr, "Added %d tasks\n", tasks);

    assert(threadpool_destroy(pool, 2) == 0);
    fprintf(stderr, "Did %d tasks\n", done);

    return 0;
}

我们在线程池中创建了32个线程,并且创建了一个长度为256的任务队列。

测试原理:
  先创建线程池,然后开始往线程池中添加任务。我们知道当任务队列中有任务时,只要线程池中有可用线程,线程池管理员就会调度线程去执行任务,所以我们就一直往任务队列中添加任务直到任务队列满了为止不再添加任务。让主程序休眠5s后销毁线程池并退出。

测试结果:
在这里插入图片描述

结果分析:
我们可以看到我们所添加的任务全部被执行完毕。




测试程序:

#define THREAD 32
#define QUEUE  256
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include "Labweek13_Threadpool.h"

#define gettid() syscall(__NR_gettid)
  /* wrap the system call syscall(__NR_gettid), __NR_gettid = 224 */
#define gettidv2() syscall(SYS_gettid) /* a traditional wrapper */


int tasks = 0, done = 0;
int count = 0;
pthread_mutex_t lock;

void worker(void *arg) {
    pthread_mutex_lock(&lock);
    done++;
    printf("thread %ld take task %d\n",gettid(),done);
    pthread_mutex_unlock(&lock);
}

int main(int argc, char **argv)
{
    threadpool_t *pool;

    pthread_mutex_init(&lock, NULL);

    assert((pool = threadpool_create(THREAD, QUEUE)) != NULL);
    fprintf(stderr, "Pool started with %d threads and "
            "queue size of %d\n", THREAD, QUEUE);

    
    while(count < 256)
    {
        threadpool_add(pool,&worker,NULL);
        printf("                     Task %d added!\n",tasks + 1);
        pthread_mutex_lock(&lock);
        count++;
        tasks++;
        
        pthread_mutex_unlock(&lock);
    }
    sleep(1);
    assert(threadpool_destroy(pool, 0) == 0);

    return 0;
}

测试原理:
  这次我们制定添加的任务数为256,当添加任务书达到256时将不再往任务队列添加任务。看线程池能否做到同步地调度子线程。
结果分析:
在这里插入图片描述

从输出我们可以看到我们一共往任务队列中添加了256个任务,线程池也同步地进行了调配并执行任务。
注意:我们在添加任务结束后最好不要立即销毁线程池,因为有可能仍有现成正在执行任务,在这个时候销毁线程池会导致有些线程在执行任务的过程中就被终止了,导致有些任务无法完成,所以在添加完任务后最好调用sleep函数让控制进程休眠一段时间直到线程全部完成任务再进行销毁线程池。

源码结构

.h文件提供了其他进程调用线程库的API
线程库的具体实现在.c文件中

.h文件如下:

#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_

#define MAX_THREADS 64
#define MAX_QUEUE 65536

typedef struct threadpool_t threadpool_t;

/* 定义错误码 */
typedef enum {
    threadpool_invalid        = -1,
    threadpool_lock_failure   = -2,
    threadpool_queue_full     = -3,
    threadpool_shutdown       = -4,
    threadpool_thread_failure = -5
} threadpool_error_t;

typedef enum {
    threadpool_graceful       = 1
} threadpool_destroy_flags_t;

/* 以下是线程池三个对外 API */

threadpool_t *threadpool_create(int thread_count, int queue_size);

int threadpool_add(threadpool_t *pool, void (*routine)(void *),
                   void *arg);


int threadpool_destroy(threadpool_t *pool, int flags);



#endif 




.c文件如下:

#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include<stdio.h>
#include"Labweek13_Threadpool.h"




/**
 * 线程池关闭的方式
 */
typedef enum {
    immediate_shutdown = 1,     //线程池立即销毁,不管任务队列中是否有任务未执行
    graceful_shutdown  = 2      //执行完任务队列中所有的任务再销毁线程池
} threadpool_shutdown_t;

/**
 * 线程池一个任务的定义
 */

typedef struct {
    void (*function)(void *);
    void *argument;
} threadpool_task_t;

//线程池的结构定义
struct threadpool_t {
  pthread_mutex_t lock;     //用于内部工作的互斥锁
  pthread_cond_t notify;    //线程间通知的条件变量
  pthread_t *threads;       //线程数组,这里用指针来表示,数组名 = 首元素指针
  threadpool_task_t *queue; //存储任务的数组,即任务队列
  int thread_count;         //线程数量
  int queue_size;           //任务队列大小
  int head;                 //任务队列中首个任务位置(注:任务队列中所有任务都是未开始运行的)
  int tail;                 //任务队列中最后一个任务的下一个位置(注:队列以数组存储,head 和 tail 指示队列位置)
  int count;                //任务队列里的任务数量,即等待运行的任务数
  int shutdown;             //表示线程池是否关闭
  int started;              //开始的线程数
};

/**
 * 线程池里每个线程在跑的函数
 * 声明 static 应该只为了使函数只在本文件内有效
 */
static void *threadpool_thread(void *threadpool);       //线程池每个线程所执行的函数

int threadpool_free(threadpool_t *pool);                //释放线程池所申请的内存资源

threadpool_t *threadpool_create(int thread_count, int queue_size);

int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg);

int threadpool_destroy(threadpool_t *pool, int flags);



threadpool_t *threadpool_create(int thread_count, int queue_size)    //创建线程池
{
    if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) {
        return NULL;
    }

    threadpool_t *pool;
    int i;

    /* 申请内存创建内存池对象 */
    pool = (threadpool_t *)malloc(sizeof(threadpool_t));
    if(pool == NULL)
    {
        printf("Threadpool creation error!\n");
        return NULL;
    }
    /* Initialize */
    pool->thread_count = 0;
    pool->queue_size = queue_size;
    pool->head = pool->tail = pool->count = 0;
    pool->shutdown = pool->started = 0;

    /* 申请线程数组和任务队列所需的内存 */
    pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
    if(pool->threads == NULL )
    {
        printf("Applying for thread memory failed!\n");
        threadpool_free(pool);
        return NULL;
    }

    pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size);
    if(pool->queue == NULL)
    {
        printf("Applying for tash queue memory failed!\n");
        threadpool_free(pool);
        free(pool->queue);
        return NULL;
    }
    /* 初始化互斥锁和条件变量 */
    if((pthread_mutex_init(&(pool->lock), NULL) != 0) || (pthread_cond_init(&(pool->notify), NULL) != 0) ) 
    {
        threadpool_free(pool);
        free(pool->threads);
        free(pool->queue);
        return NULL;
    }
   

    /* 创建指定数量的线程开始运行 */
    for(i = 0; i < thread_count; i++) {
        if(pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool) != 0) {
            threadpool_destroy(pool, 0);
            return NULL;
        }
        pool->thread_count++;
        pool->started++;
    }

    return pool;
}

int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument)    //添加需要执行的任务
{
    int err = 0;
    int next;

    if(pool == NULL || function == NULL) {
        return threadpool_invalid;
    }

    /* 必须先取得互斥锁所有权 */
    if(pthread_mutex_lock(&(pool->lock)) != 0) {
        return threadpool_lock_failure;
    }

    /* 计算下一个可以存储 task 的位置 */
    next = pool->tail + 1;
    next = (next == pool->queue_size) ? 0 : next;

    do {
        /* 检查是否任务队列满 */
        if(pool->count == pool->queue_size) {
            err = threadpool_queue_full;
            break;
        }

        /* 检查当前线程池状态是否关闭 */
        if(pool->shutdown) {
            err = threadpool_shutdown;
            break;
        }

        /* Add task to queue */
        /* 在 tail 的位置放置函数指针和参数,添加到任务队列 */
        pool->queue[pool->tail].function = function;
        pool->queue[pool->tail].argument = argument;
        /* 更新 tail 和 count */
        pool->tail = next;
        pool->count += 1;

        /* pthread_cond_broadcast */
        /*
         * 发出 signal,表示有 task 被添加进来了
         * 如果有因为任务队列空而阻塞的线程,此时会有一个被唤醒
         * 如果没有则什么都不做
         */
        if(pthread_cond_signal(&(pool->notify)) != 0) {     //通知线程池有新的任务加入任务队列
            err = threadpool_lock_failure;
            break;
        }
        /*
         * 这里用的是 do { ... } while(0) 结构
         * 保证过程最多被执行一次,但在中间方便因为异常而跳出执行块
         */
    } while(0);

    /* 释放互斥锁资源 */
    if(pthread_mutex_unlock(&pool->lock) != 0) {
        err = threadpool_lock_failure;
    }

    return err;
}

int threadpool_destroy(threadpool_t *pool, int flags)       //销毁线程池,flsg指定线程池销毁是立即销毁还是和平销毁
{
    int i, err = 0;

    if(pool == NULL) {
        return threadpool_invalid;
    }

    /* 取得互斥锁资源 */
    if(pthread_mutex_lock(&(pool->lock)) != 0) {
        return threadpool_lock_failure;
    }

    do {
        /* 判断是否已在其他地方关闭 */
        if(pool->shutdown) {
            err = threadpool_shutdown;
            break;
        }

        /* 获取指定的关闭方式 */
        pool->shutdown = (flags & threadpool_graceful) ?
            graceful_shutdown : immediate_shutdown;

        /* 唤醒所有因条件变量阻塞的线程,并释放互斥锁 */
        if((pthread_cond_broadcast(&(pool->notify)) != 0) || (pthread_mutex_unlock(&(pool->lock)) != 0)) 
        {
            err = threadpool_lock_failure;
            break;
        }

        /* 等待所有线程结束 */
        for(i = 0; i < pool->thread_count; i++) {
            if(pthread_join(pool->threads[i], NULL) != 0) {
                err = threadpool_thread_failure;
            }
        }
        /* 同样是 do{...} while(0) 结构*/
    } while(0);

    /* Only if everything went well do we deallocate the pool */
    if(!err) {
        /* 释放内存资源 */
        threadpool_free(pool);
    }
    return err;
}

int threadpool_free(threadpool_t *pool)
{
    if(pool == NULL || pool->started > 0) {
        return -1;
    }

    /* 释放线程 任务队列 互斥锁 条件变量 线程池所占内存资源 */
    if(pool->threads) {
        free(pool->threads);
        free(pool->queue);

        /* Because we allocate pool->threads after initializing the
           mutex and condition variable, we're sure they're
           initialized. Let's lock the mutex just in case. */
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_cond_destroy(&(pool->notify));
    }
    free(pool);
    return 0;
}


static void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    for(;;) {
        /* Lock must be taken to wait on conditional variable */
        /* 取得互斥锁资源 */
        pthread_mutex_lock(&(pool->lock));

        /* Wait on condition variable, check for spurious wakeups.
           When returning from pthread_cond_wait(), we own the lock. */
        /* 用 while 是为了在唤醒时重新检查条件 */
        while((pool->count == 0) && (!pool->shutdown)) 
        {
            /* 任务队列为空,且线程池没有关闭时阻塞在这里 */
            pthread_cond_wait(&(pool->notify), &(pool->lock));
        }

        /* 关闭的处理 */
        if((pool->shutdown == immediate_shutdown) || ((pool->shutdown == graceful_shutdown) && (pool->count == 0))) 
        {
            break;
        }

        /* 取得任务队列的第一个任务 */
        task.function = pool->queue[pool->head].function;
        task.argument = pool->queue[pool->head].argument;
        /* 更新 head 和 count */
        pool->head += 1;
        pool->head = (pool->head == pool->queue_size) ? 0 : pool->head;
        pool->count -= 1;

        /* 释放互斥锁 */
        pthread_mutex_unlock(&(pool->lock));

        
        /* 开始运行任务 */
        (*(task.function))(task.argument);
        /* 这里一个任务运行结束 */
    }

    /* 线程将结束,更新运行线程数 */
    pool->started--;

    pthread_mutex_unlock(&(pool->lock));
    pthread_exit(NULL);
    return(NULL);
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值