线程池 C 语言 RTOS 版本

Threadpool

  1. 前言

由于最近的项目需求,服务器端用MQTT 下发的命令,并不能马上执行完成,需要等待完成。每次执行命令都需要单独创建任务,执行完成在释放任务,效率太慢了。所以准备手写一份线程池处理。网络收集了一些C语言版本的线程池资料,基本上都是基于 linux 或者windos 自带<pthreadpool.h>线程操作函数所编写的。最后是参考视频资料,以任务方式重写一遍基于 RTOS 系统的线程池库函数。参考资料:https://www.bilibili.com/video/BV1jV411J795?p=6&spm_id_from=333.999.header_right.history_list.click&vd_source=3c0bb11f7267a381f3e9e4934427f5f3

视频中以数组方式实现,缺点是有固定大小,需要维护数组的头尾指针位置。所以改用链表的方式实现,参考资料:https://www.cnblogs.com/skywang12345/p/3562146.html

其核心思想分为三大块:

  1. 主线程,也就是线程池的管理者线程,专门用来创建和回收子线程
  2. 子线程,也称为消费者线程,专门用来处理用户下发的任务函数
  3. 任务列表,维护用户添加的任务和参数

  1. 数据定义

线程池的结构体定义,此定义维护了线程池正常运行的关键参数

// 线程池结构体
typedef struct strThreadPoolMain
{
  list_head listSon;        // 子线程链表头
  list_head listTask;       // 任务链表头
  ST_Thread *handle;        // 主线程句柄
  int minNum;               // 最小线程数
  int maxNum;               // 最大线程数
  int busyNum;              // 忙碌线程数
  int liveNum;              // 存活线程数
  int exitNum;              // 销毁线程数
  int shutdown;             // 是否需要销毁线程池, 销毁为1,不销毁为0
  ST_Mutex *mutexPool;      // 锁整个线程池
  ST_Mutex *mutexBusy;      // 锁 busyNum 变量
  ST_Semaphore *notEmpty;   // 任务队列是否空了
  list_head list;           // 链表
} strThreadPoolMain;

子线程结构体,此结构体维护正常消费者线程的运行参数

// 子线程结构体
typedef struct strThreadPoolSon
{
  int id;                   // 子线程ID
  ST_Thread *handle;        // 子线程句柄
  strThreadPoolMain *pool;  // 线程池结构体指针
  list_head list;           // 链表
} strThreadPoolSon;

任务结构体,主要是保存需要用线程执行的任务以及参数

// 任务结构体
typedef struct strThreadPoolTask
{
  void (*func)(void *arg);  // 任务地址
  void *arg;                // 任务参数
  list_head list;           // 链表
} strThreadPoolTask;

其链表维护的关系如图所示

  1. 函数实现
  1. 创建线程池 ThreadPool_create 函数

主要是申请函数线程池结构体空间以及初始化,另外就是主线程和子线程的创建。

/*******************************************************************************
 * @name        ThreadPool_create
 * @brief       Creating a thread pool
 * @param[in]   min               minimum number of threads in the thread pool
 * @param[in]   max               maximum number of threads in the thread pool
 * @retval      threadpool_t *    a pointer to a thread pool
 ******************************************************************************/
strThreadPoolMain* ThreadPool_create(int min, int max)
{
  strThreadPoolMain* pool = (strThreadPoolMain*)MG_MEM_Malloc(sizeof(strThreadPoolMain));
  do
  {
    if (pool == NULL)
    {
      APP_Printf("malloc strThreadPoolMain fail ...\n");
      return NULL;
    }
    MG_UTILS_Memset(pool, 0, sizeof(strThreadPoolMain));
    INIT_LIST_HEAD(&pool->listTask);  //任务链表头
    INIT_LIST_HEAD(&pool->listSon);   //线程链表头
    pool->minNum = min;
    pool->maxNum = max;

    pool->mutexPool = MG_OS_MutexCreate();
    pool->mutexBusy = MG_OS_MutexCreate();
    pool->notEmpty = MG_OS_SemaphoreCreate(max, 0);

    if(pool->mutexPool==NULL || pool->mutexBusy==NULL || pool->notEmpty==NULL)
    {
      APP_Printf("create mutex or semaphore fail ...\n");
      break;
    }

    MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
    // 主线程创建
    pool->handle = MG_OS_ThreadCreate("admin thread", (FU_osThreadEntry)ThreadPool_main, pool, THREAD_PRIO, THREAD_STK, THREAD_EVENT_CNT);
    if (pool->handle == NULL)
    {
      APP_Printf("MG_OS_ThreadCreate(admin thread) fail ...\n");
      break;
    }

    // 子线程创建
    int i=0;
    for (i=0; i<min; i++)
    {
      strThreadPoolSon *listSon = (strThreadPoolSon*)MG_MEM_Malloc(sizeof(strThreadPoolSon));
      if(listSon == NULL)
      {
        APP_Printf("malloc listSon fail ...\n");
        break;
      }
      char buff[64];
      MG_UTILS_Snprintf((u8*)buff, sizeof(buff), "work[%d] pthread", id);
      listSon->handle = MG_OS_ThreadCreate(buff, (FU_osThreadEntry)ThreadPool_son, listSon, THREAD_PRIO, THREAD_STK, THREAD_EVENT_CNT);
      if (listSon->handle == NULL)
      {
        APP_Printf("MG_OS_ThreadCreate(%s) fail ...\n", buff);
        break;
      }
      listSon->id = id++;
      listSon->pool = pool;
      list_add_tail(&listSon->list, &(pool->listSon));
      pool->liveNum++;
    }
    if (i < min) break;

    MG_OS_MutexUnlock(pool->mutexPool);
    return pool;
  } while (0);

  MG_OS_MutexUnlock(pool->mutexPool);

  // 释放资源
  ThreadPool_free(pool);
  return NULL;
}

1、首先是申请线程池结构体(strThreadPoolMain)空间,并清零。

2、任务链表和线程链表,先要初始化指向自己本身。

3、创建两把互斥锁, mutexPool 是整个线程池的互斥锁,让线程池变成顺序执行。mutexBusy是变量 busyNum 的互斥锁,因为这个变量是高频操作的,所以单独加锁提高执行效率。

4、创建信号量 notEmpty ,主要是当没有任务的时候,用来阻塞子线程。当需要销毁线程池的时候,每次释放一个信号量,缓存一个子线程自毁,所以信号量最大值取线程池最大值。

5、MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);

这里给线程池加锁,主要是防止在创建子线程的过程中,主线程或者子线程抢占了CPU运行,导致出差。当然这种概率发生的情况比较小。

6、当每个子线程创建完成后,调用list_add_tail(&listSon->list, &(pool->listSon)); 将新创建的子线程结构体嵌入到链表末尾。

7、创建成功后,返回线程池的指针。

8、若创建失败,调用ThreadPool_free(pool)销毁申请的资源,并返回NULL。

  1. 销毁资源 ThreadPool_free 函数

主要是释放创建线程池的时候,所申请的资源

/*******************************************************************************
 * @name        ThreadPool_free
 * @brief       Release route pool resources
 * @param[in]   pool     the thread pool you want to destroy
 * @retval      void
 ******************************************************************************/
static void ThreadPool_free(strThreadPoolMain *pool)
{
  pool->shutdown = 1;
  if (pool->handle)
  {
    // 阻塞回收管理者线程
    while (pool->shutdown != 2)
    {// 系统调度20ms一次
      MG_OS_ThreadSleep(40);
    }
  }

  if (pool->liveNum)
  {
    // 唤醒阻塞的消费者线程
    for (int i=0; i<pool->liveNum; i++)
    {
      MG_OS_SemaphoreRelease(pool->notEmpty);
    }
    // 阻塞回收消费者线程
    while (1)
    {
      if(pool->liveNum == 0) break;
      MG_OS_SemaphoreRelease(pool->notEmpty);
      MG_OS_ThreadSleep(40);
    }
  }

  if (pool->mutexPool) MG_OS_MutexDelete(pool->mutexPool);
  if (pool->mutexBusy) MG_OS_MutexDelete(pool->mutexBusy);
  if (pool->notEmpty) MG_OS_SemaphoreDelete(pool->notEmpty);
  if (pool) MG_MEM_Free(pool);
  pool = NULL;
}

这里 while (pool->shutdown != 2) 之后,需要休眠40ms

因为调用者的线程与创建的线程有可能是同级别的优先级,会导致一致抢占CPU,管理者线程得不到运行无法退出线程。另外线程调度是20ms一次,就能保证至少有一次给管理者线程执行的机会。

子线程每次唤醒都会自己退出,每次退出 liveNum 变量都会减一,当为0的时候表示所有的子线程退出完毕,if(pool->liveNum == 0) break;

剩下的就是释放互斥锁和信号量的资源。

  1. 销毁资源 ThreadPool_destroy 函数

其实就是调用 ThreadPool_free 函数销毁线程池资源

/*******************************************************************************
 * @name        ThreadPool_destroy
 * @brief       Destroy thread pool
 * @param[in]   pool     the thread pool you want to destroy
 * @retval      int      error code
 ******************************************************************************/
int ThreadPool_destroy(strThreadPoolMain *pool)
{
  if (pool == NULL)
  {
    return -1;
  }
  // 释放资源
  ThreadPool_free(pool);
  return 0;
}

  1. 子线程 ThreadPool_son 函数

其实就是消费者线程,不停的从任务队列取出任务并执行。

/*******************************************************************************
 * @name        ThreadPool_son
 * @brief       Execute task function
 * @param[in]   arg       pointer to child thread
 * @retval      void *    NULL
 ******************************************************************************/
static void *ThreadPool_son(void *arg)
{
  strThreadPoolSon *listSon = (strThreadPoolSon*)arg;
  strThreadPoolMain *pool = listSon->pool;
  while (1)
  {
    MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);

    // 当前任务队列是否为空
    while (list_empty(&pool->listTask) && !pool->shutdown)
    {
      // 阻塞工作线程
      MG_OS_MutexUnlock(pool->mutexPool);
      MG_OS_SemaphoreAcquire(pool->notEmpty, OS_DELAY_MAX);
      MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);

      // 判断是否需要销毁线程
      if (pool->exitNum > 0)
      {
        pool->exitNum--;
        if(pool->liveNum > pool->minNum)
        {
          goto _exit;
        }
      }
    }

    // 判断线程池是否被关闭了
    if (pool->shutdown)
    {
_exit:
      pool->liveNum--;
      APP_Printf("The ID of thread %lu is %d, exit\n", listSon->handle, listSon->id);
      list_del(&listSon->list);
      MG_MEM_Free(listSon);
      MG_OS_MutexUnlock(pool->mutexPool);
      MG_OS_ThreadExit();
    }

    // 从任务队列中取出一个任务
    list_head *none = pool->listTask.next;
    if (none == NULL)
    {
      MG_OS_MutexUnlock(pool->mutexPool);
    }
    else
    {
      strThreadPoolTask task;
      MG_UTILS_Memcpy(&task, list_entry(none, strThreadPoolTask, list), sizeof(strThreadPoolTask));
      list_del(none);
      MG_MEM_Free(list_entry(none, strThreadPoolTask, list));
      MG_OS_MutexUnlock(pool->mutexPool);

      // 开始工作
      APP_Printf("The ID of thread %lu is %d, start working..\n", listSon->handle, listSon->id);
      MG_OS_MutexLock(pool->mutexBusy, OS_DELAY_MAX);
      pool->busyNum++;
      MG_OS_MutexUnlock(pool->mutexBusy);

      task.func(task.arg);

      // 结束工作
      APP_Printf("The ID of thread %lu is %d, end working..\n", listSon->handle, listSon->id);
      MG_OS_MutexLock(pool->mutexBusy, OS_DELAY_MAX);
      pool->busyNum--;
      MG_OS_MutexUnlock(pool->mutexBusy);
    }
  }
  return NULL;
}

1、这里用while 不用 if ,主要是保证每次唤醒子线程,一定是有任务的情况下往下执行,否则有可能同时唤醒两个子线程,只有一个任务,则会出错。

while (list_empty(&pool->listTask) && !pool->shutdown)

  1. 如果有线程库文件的话,可以使用条件变量,pthread_cond_wait(&pool->notEmpty, &pool->mutexPool); 一句话就能执行阻塞动作。现在只能是用三句话,使用信号量来完成阻塞子线程,

      MG_OS_MutexUnlock(pool->mutexPool);

      MG_OS_SemaphoreAcquire(pool->notEmpty, OS_DELAY_MAX);

      MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);

3、如果有需要销毁子线程操作,或者线程池被销毁了,会自动调整到 _exit 处,释放资源,退出任务。

4、取任务执行的时候,这里使用的是链表的经典宏定义 list_entry 

list_entry(none, strThreadPoolTask, list) ,计算结构体 strThreadPoolTask 中,元素list的偏移量,然后用 none 的地址减去偏移,既得到该结构体 strThreadPoolTask 变量的真实地址。拷贝出来后,再删除该none节点,释放该变量资源。

5、剩下的就是直接调用函数指针,正真执行用户传进来的任务。busyNum 这个变量,在执行加减操作的时候,有个专门的互斥锁(mutexBusy),进行保护。

  1. 主线程 ThreadPool_main 函数

线程管理者函数,主要功能是管理线程的创建和销毁动作。

/*******************************************************************************
 * @name        ThreadPool_main
 * @brief       Create and destroy threads
 * @param[in]   arg       a pointer to a thread pool
 * @retval      void *    NULL
 ******************************************************************************/
static void *ThreadPool_main(void *arg)
{
  strThreadPoolMain *pool = (strThreadPoolMain*)arg;
  while (!pool->shutdown)
  {
    // 每隔1秒间隔工作一次
    MG_OS_ThreadSleep(1000);

    // 取出线程池中任务的数量和当前线程的数量
    MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
    list_head *node, *temp;
    int taskSize = 0;
    int liveNum = 0;
    int busyNum = 0;
    list_for_each_safe (node, temp, &(pool->listTask))
    {
      taskSize++;
    }
    // list_for_each_safe (node, temp, &(pool->listSon))
    // {
    //   liveNum++;
    // }
    liveNum = pool->liveNum;  // <- 取出活的线程的数量
    busyNum = pool->busyNum;  // <- 取出忙的线程的数量
    MG_OS_MutexUnlock(pool->mutexPool);

    // 添加线程 (任务的个数 > 存活的线程个数  &&  存活的线程 < 最大线程数)
    if (taskSize>liveNum && liveNum<pool->maxNum)
    {
      MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
      for (int i=0; i<NUMBER && pool->liveNum<pool->maxNum; i++)
      {
        strThreadPoolSon *listSon = (strThreadPoolSon*)MG_MEM_Malloc(sizeof(strThreadPoolSon));
        if(listSon == NULL)
        {
          APP_Printf("malloc listSon fail ...\n");
          break;
        }
        char buff[64];
        MG_UTILS_Snprintf((u8*)buff, sizeof(buff), "work[%d] pthread", id);
        listSon->handle = MG_OS_ThreadCreate(buff, (FU_osThreadEntry)ThreadPool_son, listSon, THREAD_PRIO, THREAD_STK, THREAD_EVENT_CNT);
        if(listSon->handle == NULL)
        {
          APP_Printf("MG_OS_ThreadCreate(%s) fail ...\n", buff);
          break;
        }
        listSon->id = id++;
        listSon->pool = pool;
        list_add_tail(&listSon->list, &(pool->listSon));
        pool->liveNum++;
      }
      MG_OS_MutexUnlock(pool->mutexPool);
    }

    // 销毁线程 (忙的线程*2 < 存活的线程  &&  存活的线程 > 最小线程数)
    if (busyNum * 2 < liveNum && liveNum > pool->minNum)
    {
      MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
      pool->exitNum = NUMBER;
      MG_OS_MutexUnlock(pool->mutexPool);

      // 让工作的线程自杀
      for (int i=0; i<NUMBER; i++)
      {
        MG_OS_SemaphoreRelease(pool->notEmpty);
      }
    }
  }
  pool->shutdown = 2;
  APP_Printf("pool->shutdown = %d\n", pool->shutdown);
  APP_Printf("MG_OS_ThreadExit(%#X);\n", pool->handle);
  MG_OS_ThreadExit();
  return NULL;
}

管理者线程不需要频繁执行,每间隔几秒后,动态的调整当前子线程的数量即可。

  1. 这里调用的是链表宏定义list_for_each_safe (node, temp, &(pool->listTask)),遍历整个链表,计算任务数量。活着的子线程也可以这样操作,但是直接获取 liveNum 执行效率更快。
  2. 创建子线程的条件,可以按自己需求制定策略。这里以任务数量大于存活子线程的时候,每次创建两个子线程。

3、具体的创建子线程过程与初始化的时候一样,当每个子线程创建完成后,调用list_add_tail(&listSon->list, &(pool->listSon)); 将新创建的子线程结构体嵌入到链表末尾。

4、销毁线程的条件,可以按自己需求制定策略。这里以忙碌的线程两倍数量,还是小于存活的子线程数量,开始销毁子线程,每次销毁两个子线程。销毁的时候,将exitNum变量赋值为2 ,然后通过信号量唤醒子线程,由子线程自己释放结构体资源,退出线程。

5、管理者线程在正常工作的时候不会退出,只有当需要销毁线程池的时候,pool->shutdown 有效,先赋值为2通知其调用着,本主线程已经退出。

  1. 添加任务ThreadPool_add_task 函数
/*******************************************************************************
 * @name        ThreadPool_add_task
 * @brief       Add task function
 * @param[in]   pool      a pointer to a thread pool
 * @param[in]   func      address of task function
 * @param[in]   arg       parameters of task function
 * @retval      int       error code
 ******************************************************************************/
int ThreadPool_add_task(strThreadPoolMain *pool, void(*func)(void*), void *arg)
{
  strThreadPoolTask *listTask = (strThreadPoolTask*)MG_MEM_Malloc(sizeof(strThreadPoolTask));
  if (listTask == NULL)
  {
    APP_Printf("malloc listTask fail ...\n");
    return -1;
  }
  listTask->func = func;
  listTask->arg = arg;
  MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
  list_add_tail(&listTask->list, &(pool->listTask));
  MG_OS_MutexUnlock(pool->mutexPool);
  MG_OS_SemaphoreRelease(pool->notEmpty);
  return 0;
}
  1. 首先是申请内存空间,将用户传递进来的函数和参数都保存起来
  2. 在加锁 mutexPool 锁定整个线程池,将新增加的任务插入到任务链表的末尾后解锁

3、最后释放信号量,唤醒消费者线程执行任务。

  1. 获取忙碌线程个数ThreadPool_busy_num 函数

加锁后获取 busyNum 变量再解锁

/*******************************************************************************
 * @name        ThreadPool_busy_num
 * @brief       Get the number of busy threads in the thread pool
 * @param[in]   pool      a pointer to a thread pool
 * @retval      int       number of busy threads
 ******************************************************************************/
int ThreadPool_busy_num(strThreadPoolMain *pool)
{
  MG_OS_MutexLock(pool->mutexBusy, OS_DELAY_MAX);
  int busyNum = pool->busyNum;
  MG_OS_MutexUnlock(pool->mutexBusy);
  return busyNum;
}

  1. 获取存活线程个数ThreadPool_alive_num 函数

加锁后获取 liveNum变量再解锁

/*******************************************************************************
 * @name        ThreadPool_alive_num
 * @brief       Get the number of threads surviving in the thread pool
 * @param[in]   pool      a pointer to a thread pool
 * @retval      int       number of alive threads
 ******************************************************************************/
int ThreadPool_alive_num(strThreadPoolMain *pool)
{
  MG_OS_MutexLock(pool->mutexPool, OS_DELAY_MAX);
  int aliveNum = pool->liveNum;
  MG_OS_MutexUnlock(pool->mutexPool);
  return aliveNum;
}

最后上测试代码,创建线程池,最小3个线程,最大10个线程,创建100个任务。

struct taskData
{
  int id;
  int num;
};

void taskFunc(void *arg)
{
  struct taskData *data = (struct taskData*)arg;
  // APP_Printf("thread %ld is working, number = %d\n", pthread_self(), num);
  APP_Printf("thread data->id is %ld working, number = %d\n", data->id, data->num);
  MG_OS_ThreadSleep(1000);
}

int testThreadPool(void)
{
  // 创建线程池
  APP_Printf("start test thread pool\n");
  strThreadPoolMain *pool = ThreadPool_create(3, 10);
  for (int i=0; i<100; i++)
  {
    struct taskData *data = (struct taskData*)MG_MEM_Malloc(sizeof(struct taskData));
    data->id = i;
    data->num = i+100;
    ThreadPool_add_task(pool, taskFunc, data);
  }
  MG_OS_ThreadSleep(25*1000);
  for (int i=200; i<210; i++)
  {
    struct taskData *data = (struct taskData*)MG_MEM_Malloc(sizeof(struct taskData));
    data->id = i;
    data->num = i+200;
    ThreadPool_add_task(pool, taskFunc, data);
  }
  MG_OS_ThreadSleep(5*1000);
  ThreadPool_destroy(pool);
  APP_Printf("end test thread pool\n");
  return 0;
}

最后实测效果如下:

 

 刚开始以最小任务运行,每次执行3个任务,当1秒钟后主线程唤醒,创建了2个新的线程,变成5个线程同时执行任务。直到循环创建满线程10个一起工作。

 最后当所有任务执行完毕后,关闭了7个子线程,保留最小3个子线程阻塞等待任务。当执行销毁线程池动作后,会先关闭主线程,然后关闭所有子线程,最后释放互斥锁和信号量,完整退出销毁线程池动作。

初步看,该线程池能工作正常,能满足需求了。

资源下载地址:线程池C语言RTOS版本-嵌入式文档类资源-CSDN下载

Hankin

2022.07.26

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C8051 RTOS指的是C8051系列微控制器的实时操作系统。C8051是Silicon Laboratories公司生产的一种集成了处理器、内存和外设的微控制器解决方案,能够广泛应用于嵌入式系统或物联网设备中。 RTOS(Real-Time Operating System)是一种能够提供实时任务调度和管理的操作系统。与传统的操作系统相比,RTOS更注重实时性能和响应能力,适用于对任务处理时间要求严格的应用场景。而C8051 RTOS则是专为C8051系列微控制器设计的实时操作系统。 C8051 RTOS具有以下特点: 1. 实时性能:C8051 RTOS通过任务调度算法,确保在多个并发任务同时进行时,能够满足任务处理时间要求,确保任务的实时性。 2. 资源管理:C8051 RTOS可以高效地管理处理器、内存和外设等硬件资源,使得各个任务能够有效共享和利用资源,提高系统的整体性能。 3. 任务调度:C8051 RTOS中的任务调度器能够根据任务的优先级和进程状态,按照一定的调度策略分配处理器时间片,保证高优先级任务得到及时处理,提高系统的处理效率和响应速度。 4. 同步与通信:C8051 RTOS提供了一些同步和通信机制,比如信号量、互斥锁、消息传递等,方便任务之间的数据共享和通信,确保任务之间的协作能够顺利进行。 5. 可裁剪性:C8051 RTOS可以根据应用需求进行裁剪,只保留必要的组件和功能,减小系统的资源占用,提高系统效率。 总之,C8051 RTOS是一种针对C8051系列微控制器设计的实时操作系统,具有较高的实时性能、资源管理能力、任务调度机制和同步通信能力,可以满足嵌入式系统和物联网设备对实时性和可靠性的要求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值