Linux下的简单线程池

2023-05-16

问题描述:

    在我们的日常生活中,如果我们现在要浏览一个网页或者频繁的打开一个执行时间较短的任务,

如果每次调用都创建一个线程,使用结束后就立即释放,那么这样的开销对于操作系统来说有点太过浪费了。

而线程池的出现就可以很好地解决这样的问题。

线程池:

1.特点:

1>经典生产者消费者模型。

2>线程池中有若干等待的线程。

3>线程池中的线程用于执行大量的相对短暂的任务。

 

2.功能描述:

当任务增加时,可以动态增加线程池中线程的个数,

当任务执行完成后,可以动态的减少线程池中线程的个数。

生产者线程向任务队列中添加任务,任务队列中有任务,如果有等待线程就唤醒并执行任务

如果线程池中没有等待线程并且没有达到上限,就添加新的线程到线程池

3.线程池的创建场景:

1>计算密集型任务: 线程池线程个数 = CPU个数

2>I/O密集型任务:线程池线程个数 > CPU个数

线程池的创建思路:

线程池的结构体框架:

//任务队列整体的结构体
typedef struct threadpool{
    condition_t cond;       //同步、互斥
    task_t* first;          //任务队列的队头
    task_t* tail;           //任务队列的队尾
    int max_thread;         //最大线程数
    int idle;           //空闲线程个数
    int counter;        //线程池当前的线程个数
}threadpool_t;

任务队列结点的结构体:

typedef struct task{
    void* (*pfun)(void*);    //任务队列的回调函数
    void* arg;              //回调函数的参数
    struct task* _next;
}task_t;

互斥量的结构体:

//互斥操作的结构体
typedef struct condition{   
      pthread_mutex_t pmutex;
      pthread_cond_t pcond;
}condition_t;

数据组织的数据结构:先来的任务先执行,后来的任务后执行。所以选择队列。

对线程池的操作:

1.

线程池初始化:需要给出要初始化线程池对象,线程池线程的最大数量

void threadpool_init(threadpool_t* pool, int max);

添加任务:需要给出线程池,任务函数,及任务函数的参数

void threadpool_add(threadpool_t* pool, void*(*pf)(void*), void* arg);

销毁线程池:需要给出线程池(动态的销毁线程池---当一定时间内,某线程一直没有被调用过,认为可以将该线程销毁)

void threadpool_destroy(threadpool_t* pool);

线程池完整代码:

1.main.c

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

#include "threadpool.h"

//主函数功能描述:
//创建一个线程池,初始化最大线程3, 添加10个任务

void* run(void* arg)
{
  int id = *(int*)arg;
  free(arg);
  printf("%#lX thread running ! id = %d\n", pthread_self(), id);
  sleep(1);
}

int main()
{
  threadpool_t pool;
  threadpool_init(&pool, 3);
  int i = 0;
  for(; i < 10; i++)
  {
    int *p = (int*)malloc(sizeof(int));
    *p = i;
    threadpool_add(&pool, run, (void*)p);
  }
  threadpool_destroy(&pool);
  sleep(15);
}

2.threadpool.h

#ifndef __THREADPOOL_H__
#define __THREADPOOL_H__

#include "condition.h"

typedef struct task{        //队列结点的结构体
    void* (*pfun)(void*);   //任务队列的回调函数
    void* arg;              //回调函数的参数
    struct task* _next;     //链表指针
}task_t;

typedef struct threadpool{
    condition_t cond;     //同步、互斥操作
    task_t* first;        //任务队列的队头
    task_t* tail;         //任务队列的队尾
    int max_thread;       //最大线程数
    int idle;             //空闲线程的个数
    int counter;          //线程池当前的线程个数
    int quit;          //为1表示退出,为0表示不退出
}threadpool_t;

//初始化
void threadpool_init(threadpool_t* pool, int max);

//往线程池中添加任务
void threadpool_add(threadpool_t* pool, void*(*pf)(void*), void* arg);

//销毁线程池(动态的销毁线程池---当一定时间内,某线程一直没有被调用过,认为可以将该线程销毁)
void threadpool_destroy(threadpool_t* pool);

#endif// __THREADPOOL_H__

3.threadpool.c

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

#include "threadpool.h"
//线程池功能描述:
//当任务增加时,可以动态增加线程池中线程的个数,
//当任务执行完成后,可以动态的减少线程池中线程的个数。

//生产者线程向任务队列中添加任务,任务队列中有任务,如果有等待线程就唤醒并执行任务,
//如果线程池中没有等待线程并且没有达到上限,就添加新的线程到线程池


//线程执行的任务函数
void* thread_fun(void* arg)   
{
  threadpool_t* pool = (threadpool_t*)arg;
  int timeout = 0;
  while(1)    //线程没有任务也不退出
  {
    condition_lock(&pool->cond);
    pool->idle++;   //线程没有执行任务,先置为空闲线程
    timeout = 0;
    while(pool->first == NULL && pool->quit == 0)
    {
      struct timespec ts;
      clock_gettime(CLOCK_REALTIME, &ts);   //获取当前的绝对时间。
      ts.tv_sec += 2;         
      int r = condition_timedwait(&pool->cond, &ts);
      if(r == ETIMEDOUT)
      {
        timeout = 1;
        break;
      }
    }
    pool->idle--;
    
    if(NULL != pool->first)
    {
      task_t *p_first = pool->first;      //取出队头结点
      pool->first = p_first->_next;

      condition_unlock(&pool->cond);  //防止调用的任务执行时间过长,导致别的线程无法进入22行的lock,甚至于往线程池添加任务的锁都无法进入
      //这里的解锁不会影响对链表的操作,因为这里将链表中的单个结点拿出来进行操作,并没有涉及到对链表的操作(没有操作线程之间共享的数据)
      (p_first->pfun)(p_first->arg);  //调用pool传递进来的任务函数  
      condition_lock(&pool->cond);

      free(p_first);
      p_first = NULL;
    }
    
    if(NULL == pool->first && 1 == timeout) //防止其他线程添加任务,这时应该去执行相应的任务,
    {
      //此时确定是线程2秒内没有任务,导致的退出
      condition_unlock(&pool->cond);
      printf("%#lX thread TIMEOUT!  QUIT!!!\n", pthread_self());
      break;
    }

    if(pool->quit == 1 && NULL == pool->first)
    {
      printf("%#lX thread destroy!\n", pthread_self());
      pool->counter--;
      if(0 == pool->counter)
      {
        condition_signal(&pool->cond);  //这里发送信号提示最后一个线程任务结束
      }
      condition_unlock(&pool->cond);
      break;
    }
    condition_unlock(&pool->cond);
  }
}

void threadpool_init(threadpool_t* pool, int max)
{
  assert(NULL != pool);
  condition_init(&pool->cond);
  pool->first      = NULL;
  pool->tail       = NULL;
  pool->max_thread = max;
  pool->idle       = 0;
  pool->counter    = 0;
  pool->quit       = 0;
}
//往线程池添加线程
void threadpool_add(threadpool_t* pool, void*(*pf)(void*), void* arg)
{
  //先将任务生成任务节点
  task_t* new_task = (task_t*)malloc(sizeof(task_t));
  new_task->pfun   = pf;
  new_task->arg    = arg;
  new_task->_next  = NULL;
  
  //由于队列的插入操作、及线程池线程的调用操作是不能被打断的
  //所以以下动作需加锁
  condition_lock(&pool->cond);
  //将任务插入任务队列等待调度
  
  if(NULL == pool->first)       
  {
    pool->first = new_task;
  }
  else
  {
    pool->tail->_next = new_task;
  }

  pool->tail  = new_task;     //跳整队尾指针
  //当线程池中存在空闲线程时,直接唤醒空闲线程
  if(pool->idle > 0)      //有空闲线程
  {
    condition_signal(&pool->cond);
  }
  //当线程池中没有空闲线程且线程总数没有超出最大线程,则创建线程并执行
  else if(pool->counter < pool->max_thread)
  {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_fun, (void*)pool);
    pool->counter++;
  }

    condition_unlock(&pool->cond);
}

void threadpool_destroy(threadpool_t* pool)
{
  if(pool->quit) return;

  condition_lock(&pool->cond);
  
  pool->quit = 1;     //将所有的线程状态置为退出态
  if(pool->counter > 0)
  {
    if(pool->idle > 0)
    condition_broadcast(&pool->cond);
  }
  while(pool->counter > 0)
  {
    condition_wait(&pool->cond);
  }
  condition_unlock(&pool->cond);
  condition_destroy(&pool->cond);
}

4.condition.h

#ifndef __CONDITION_H__
#define __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_timedwait(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// __CONDITION_H__

5.condition.c

#include <pthread.h>

#include "condition.h"

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

int condition_lock(condition_t* cond)
{
 return  pthread_mutex_lock(&cond->pmutex);
}

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

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

int condition_timedwait(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)
{
  return pthread_mutex_destroy(&cond->pmutex) &&
  pthread_cond_destroy(&cond->pcond);
}

6.Makefile

.PHONY: all clean

all: test

test: condition.o main.o threadpool.o
	gcc $^ -o $@ -lpthread -lrt

%.o:%.c
	gcc -c $^ -o $@

clean:
	rm -rf *.o test 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下的简单线程池 的相关文章

  • 安装J语言的JQt IDE,出现错误

    我一直按照这里的说明进行操作 http code jsoftware com wiki System Installation Linux http code jsoftware com wiki System Installation L
  • jpegtran 优化而不更改文件名

    我需要优化一些图像 但不更改它们的名称 jpegtran copy none optimize image jpg gt image jpg 但是 这似乎创建了 0 的文件大小 当我对不同的文件名执行此操作时 大小仍然完全相同 怎么样 jp
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • jq中如何分组?

    这是 json 文档 name bucket1 clusterName cluster1 name bucket2 clusterName cluster1 name bucket3 clusterName cluster2 name bu
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 查找哪个程序运行另一个程序

    我有一个 NAS 运行在 Redhat Linux 的有限版本上 我按照指示破解了它 这样我就可以访问 shell 这很有帮助 我还做了一些修改 其他人也做过修改 除了一个问题之外 它们似乎都工作得很好 不知何故 每隔 22 天 系统就会关
  • 如何更改 Apache 服务器的根目录? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何更改 Apache 服务器的文档根目录 我基本上想要localhost从 来 users spencer projects目录而不是
  • 如何在 Bash 中给定超时后终止子进程?

    我有一个 bash 脚本 它启动一个子进程 该进程时不时地崩溃 实际上是挂起 而且没有明显的原因 闭源 所以我对此无能为力 因此 我希望能够在给定的时间内启动此进程 如果在给定的时间内没有成功返回 则将其终止 有没有simple and r
  • MySQL 中的创建/写入权限

    我的设备遇到一些权限问题SELECT INTO OUTFILE陈述 当我登录数据库并执行简单的导出命令时 例如 mysql gt select from XYZ into outfile home mropa Photos Desktop
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • 进程退出后 POSIX 名称信号量不会释放

    我正在尝试使用 POSIX 命名信号量进行跨进程同步 我注意到进程死亡或退出后 信号量仍然被系统打开 在进程 打开它 死亡或退出后是否有办法使其关闭 释放 早期的讨论在这里 当将信号量递减至零的进程崩溃时 如何恢复信号量 https sta
  • 监视目录的更改

    很像一个类似的问题 https stackoverflow com questions 112276 directory modification monitoring 我正在尝试监视 Linux 机器上的目录以添加新文件 并希望在这些新文
  • 确定我可以向文件句柄写入多少内容;将数据从一个 FH 复制到另一个 FH

    如何确定是否可以将给定数量的字节写入文件句柄 实际上是套接字 或者 如何 取消读取 我从其他文件句柄读取的数据 我想要类似的东西 n how much can I write w handle n read r handle buf n a
  • 快速像素绘图库

    我的应用程序以每像素的方式生成 动画 因此我需要有效地绘制它们 我尝试过不同的策略 库 但结果并不令人满意 尤其是在更高分辨率的情况下 这是我尝试过的 SDL 好的 但是慢 OpenGL 像素操作效率低下 xlib 更好 但仍然太慢 svg
  • 高效的内存屏障

    我有一个多线程应用程序 其中每个线程都有一个整数类型的变量 这些变量在程序执行期间递增 在代码中的某些点 线程将其计数变量与其他线程的计数变量进行比较 现在 我们知道在多核上运行的线程可能会无序执行 一个线程可能无法读取其他线程的预期计数器
  • Capistrano 3 部署无法连接到 GitHub - 权限被拒绝(公钥)

    我使用 Capistrano v3 和 capistrano symfony gem 设置了以下部署脚本 我正在使用 Ubuntu 14 4 部署到 AWS EC2 实例 我正在连接从 AWS 下载的 pem 文件 我的deploy rb中
  • 限制 Imagemagick 使用的空间和内存

    我在 Rails 应用程序上使用 Imagemagick 使用 rmagick 但我的服务器 Ubuntu 不是很大 当我启动转换进程时 Imagemagick 占据了我的服务器 30GB HDD 的所有位置 内存 我想限制内存和 tmp
  • 后台分叉无法正常工作[重复]

    这个问题在这里已经有答案了 我运行这个程序 在前景和背景中 int main int pid printf App Start pid d n getpid while 1 pid fork if pid 0 printf Child n
  • Linux 上的 RTLD_LOCAL 和dynamic_cast

    我们有一个由应用程序中的一些共享库构成的插件 我们需要在应用程序运行时更新它 出于性能原因 我们在卸载旧插件之前加载并开始使用新插件 并且只有当所有线程都使用旧插件完成后 我们才卸载它 由于新插件和旧插件的库具有相同的符号 我们dlopen
  • Fedora dnf 更新不起作用?

    当我尝试使用 update 命令更新 Fedora 22 时 sudo dnf update 我收到以下错误 错误 无法同步存储库 更新 的缓存 无法准备内部镜像列表 Curl 错误 6 无法解析主机名 无法解析主机 mirrors fed

随机推荐

  • 【MATLAB】多个子图之间colorbar不共享问题解决方法

    在实验时 xff0c 发现subplot后每个子图的colorbar不统一 xff0c 于是一番百度 找到解决方法 xff1a 在绘制每个子图后 xff0c 使用caxis 0 1 命令 xff0c 成功解决 for example spa
  • 手把手教你调整电脑磁盘的分区大小

    教程目录 一 前言二 准备工具三 注意事项四 分区步骤 一 前言 我们电脑在使用过程中会下载很多软件 xff0c 尤其是我们计算机专业 xff0c 那不各种IDE装起来 xff0c 这时候会导致某个盘的空间不足 xff0c 需要给它再分些空
  • VirtualBox快照创建

    1 点击控制 gt 生成备份系统快照 2 添加快照名称和描述 xff0c 方便以后还原 3 等待一会 4 备份完成后可在VirtualBox管理器中看到生成的备份
  • putty连接出现remote side unexpected closed network-connection错误

    putty远程连接Red Hat Enterprise Linux 出现remote side unexpected closed network connection错误 查看日志文件 val log secure span class
  • IDEA 创建Servlet项目

    1 打开IDEA xff0c 点击Create New project创建一个一个新项目 2 点击Java Enterprise xff0c 然后选择Web Application xff0c 点击Next 3 设置项目名 xff0c 项目
  • 数据库接口类和接口实现类

    数据库接口类 xff08 BasicDAO java xff09 xff1a 实现对数据库的直接增删查改的interface接口 span class token keyword import span java span class to
  • Linux安装Anaconda

    Anaconda是一个开源的Python发行版本 xff0c 其包含了conda Python等180多个科学包及其依赖项 一 安装Anaconda 1 下载Anaconda安装包 xff08 我的位置是hadoop的家目录 xff0c 即
  • Windows 安装Maven3.6.1

    Win10 安装Maven3 6 1 xff0c 并为IntelliJ IDEA配置本地maven 一 安装Maven二 配置Maven本地仓库三 为IntelliJ IDEA配置本地maven 一 安装Maven 1 前提安装好jdk 2
  • 使用gorm创建casbin数据库报错

    1 报错 span class token operator span github span class token punctuation span com span class token operator span casbin s
  • Java 操作HBase

    Java 操作HBase 思路 1 建立连接 2 针对表的操作 xff08 创建表 删除表 判断表是否存在 使用 禁用表 列出表 xff09 3 针对数据的操作 xff08 添加 删除 修改 查看 xff09 4 关闭连接 HBase常用的
  • strtok和strtok_s函数使用说明

    看了很多高赞CSDN文章和百度百科 xff0c 越看越晕 xff0c 浪费好多时间 xff0c 特此记录 先介绍strtok xff0c 后边给个strtok s的例子 注意 xff1a 这两个函数必要连续调用多次才能实现分割和输出功能 x
  • chmod修改权限的用法

    一 chmod作用 xff1a 修改文件 目录的权限 二 语法 xff1a chmod 对谁操作 操作符 赋予的权限 文件名 三 操作对象 xff1a u 用户user xff0c 表现文件或目录的所有者 g 用户组group xff0c
  • 一篇文章入门Stm32CubeMX在freertos系统下进行uart串口通讯

    相信大部分人早期入门STM32系列单片机都是从各种例程入手的 xff0c STM32单片机繁多的寄存器已经不允许我们像学51系列单片机一样直接操作寄存器了 xff08 如果你记忆力好 xff0c 或者愿意花很多时间翻芯片手册查看对应寄存器的
  • Note: Python_Matplotlib绘制平滑曲线和散点图

    给出横坐标纵坐标点 xff0c 即可连线绘图 import matplotlib 调用绘图工具包 给出x y点坐标 x y 61 1 2 3 4 5 6 5 9 3 4 7 5 绘图 matplotlib pyplot plot x y 这
  • Word论文中设置正文中的引用参考文献 按住Ctrl键+单击鼠标右键 实现跳转到论文参考文献的对应位置

    Word论文中设置正文中的引用参考文献 按住Ctrl键 43 单击鼠标右键 实现跳转到论文参考文献的对应位置 首先要确保文中参考文献排版是插入的编号 xff0c 而不是自己手敲的 1 2 在正文要引用参考文献的位置 点击上方菜单栏的 插入
  • NVM 安装node.js后没有npm

    我们在使用NVM管理工具安装一个新的node后 xff0c 发现没有npm可以使用 参考文档 是因为在使用NVM安装node的时候不会默认安装npm xff0c 所以需要我们自己下载后放到nvm对应的node目录下面 npm下载地址 xff
  • idea项目设置鼠标右键点击文件夹通过IDEA打开

    每次打开idea项目是每次都要打开idea再手动选择项目 xff0c 直接设置成右键打开会很方便 效果图 xff1a 1 首先 win 43 R 输入regedit 打开注册表 2 打开注册表后找到如下路径 xff1a 计算机 HKEY L
  • Java利用Stream统计List中每个元素的个数

    1 传统HashMap 新建HashMap然后for循环List去统计每个元素出现的次数的方法实现 public static Map lt String Integer gt frequencyOfListElements List it
  • Git操作一直要求输入用户名和密码

    通过如下命令配置 xff1a git config global credential helper store git config global user email git config global user name 配置好后再去
  • Linux下的简单线程池

    问题描述 xff1a 在我们的日常生活中 xff0c 如果我们现在要浏览一个网页或者频繁的打开一个执行时间较短的任务 xff0c 如果每次调用都创建一个线程 xff0c 使用结束后就立即释放 xff0c 那么这样的开销对于操作系统来说有点太