Linux C/C++ 开发(学习笔记五):线程池

简介: Linux C/C++ 开发(学习笔记五):线程池

一、线程池的由来和组成

如果涉及到十万个线程,根本没办法开这么多

如果一个poxis线程占8M内存,那么16G内存也只能有2048个线程,根本没法满足十万线程的需求。

线程池

1.避免线程太多,使得内存耗尽

2.避免创建与销毁线程的代价

3.任务与执行分离

所以需要 任务与执行 分离。

一个例子,对于营业厅来说,办理业务的人是任务队列,柜员 是执行队列

因此需要一个组件(也就是线程池),使得任务队列和执行队列,正常有序地工作

线程池组成

  1. 任务队列
  2. 执行队列
  3. 管理组件----》锁

线程池API

1.create/init

2.push_task

3.destroy/deinit

二、实现一个基本的线程池

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
***********链表操作的宏定义**************
//头部插入
#define LIST_INSERT(item,list) do{  \
    item->prev=NULL;             \
    item->next=list;                \
    if((list)!=NULL) (list)->prev=item; \
    (list)=item;\
}while(0)
//删除节点(如果指只剩下一个节点的时候要特殊考虑)
#define LIST_REMOVE(item,list) do{    \
    if(item->prev!=NULL) item->prev->next=item->next; \
    if(item->next!=NULL) item->next->prev=item->prev; \
    if(list==item) list=item->next; \
    item->prev=item->next=NULL; \
}while(0)
********************end******************
struct nTask{
    void (*task_func)(nTask* task);//函数指针
    void *user_data;//处理的数据(要传入到上面的函数中去)
    //用双向链表来表示任务队列
    nTask *prev; 
    nTask *next;
};
struct nWorker{
    pthread_t threadId;
    struct nManager *manager;//有利于worker可以更改线程池
    int terminate;//是否中止
    nWorker *prev;
    nWorker *next;
};
//管理组件,也就是线程池
typedef struct nManager{
    nTask *tasks;//任务队列的首节点
    nWorker *workers;//执行队列的首节点
    pthread_mutex_t mutex;//互斥锁
    pthread_cond_t cond;//条件变量 (比如没有来任务的时候,worker需要做什么呢,所以需要这个变量)
}ThreadPool;
//callback!=task     
static void* nThreadPoolCallBack(void* arg){
    //worker一直在判断任务队列里面有没有任务(等待任务到来),一旦有任务,就会从任务队列取出任务来。
    nWorker* worker=(nWorker*)arg;
    //1.判断有无等待的任务(任务队列),2.如果有任务就把任务分配给这个worker,3.执行任务
    while(true){//当任务不为空的时候,就会往下走,不然会一直等着任务来。
        pthread_mutex_lock(&worker->manager->mutex);//上锁
        while(worker->manager->tasks==NULL){
            if(worker->terminate) break;//中断(2处地方可以:1.等待的时候 2.有任务,还未分配任务的时候)
            pthread_cond_wait(&worker->manager->cond,&worker->manager->mutex);//等待
        }
        if(worker->terminate){
            pthread_mutex_unlock(&worker->manager->mutex);//中断的话记得解锁,不然会造成死锁
            break;
        } 
        //如果任务不为空,那么将任务队列,首个任务取出来
        nTask* task=worker->manager->tasks;
        if(task!=NULL){
            LIST_REMOVE(task,worker->manager->tasks);
        }
        pthread_mutex_unlock(&worker->manager->mutex);//解锁
    if(task==NULL) continue;//如果terminate,会出现task==NULL的情况
        task->task_func(task);//执行任务
    }
    delete worker;//由于存在线程销毁,可以到这一步
}
//API
int nThreadPoolCreate(ThreadPool* pool,int numWorkers){
    if(pool==NULL) return -1;
    if(numWorkers<1) numWorkers=1;//如果初始化 worker小于1,就默认给他1,员工小于1的话,这个线程池是没法工作的
    memset(pool,0,sizeof(ThreadPool));
    //初始化 条件变量
    pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
    pool->cond=blank_cond;
    //初始化 互斥锁
    pthread_mutex_init(&pool->mutex,NULL);
    //初始化work
    for(int i=0;i<numWorkers;i++){
        nWorker *worker=(struct nWorker*)malloc(sizeof(struct nWorker));
        if(worker==NULL){//如果创建失败
            perror("malloc");
            return -2;
        }
        memset(worker,0,sizeof(nWorker));//将堆上创建的数据全部置0
        worker->manager=pool;//方便后续worker去操作线程池
        int ret=pthread_create(&worker->threadId,NULL,nThreadPoolCallBack,worker);//每个worker执行的工作是一样的,但是任务是不一样的
        if(ret){//posixthread有个特点,创建失败会返回非0
            perror("pthread_create");
            delete worker;
            return -3;
        }
        LIST_INSERT(worker,pool->workers);//把worker插入到 执行队列中
    }
    //success
    return 0;
}
//API
int nThreadPoolDestory(ThreadPool* pool,int numWorkers){
    nWorker* worker=NULL;
    for(worker=pool->workers;worker!=NULL;worker=worker->next){
        worker->terminate=1;
    }
    //防止死锁
    pthread_mutex_lock(&pool->mutex); //广播的时候和等待的时候用的是同一把锁
    pool->workers=NULL;
    pool->tasks=NULL;
    pthread_cond_broadcast(&pool->cond);//唤醒所有等待这个条件的
    pthread_mutex_unlock(&pool->mutex);
    return 0;   
}
//API
int nThreadPoolPushTask(ThreadPool* pool,nTask* task){
    pthread_mutex_lock(&pool->mutex);
    LIST_INSERT(task,pool->tasks);
    pthread_cond_signal(&pool->cond);//唤醒等待队列中的一个
    pthread_mutex_unlock(&pool->mutex);
}
//
// sdk --> debug thread pool
#if 1
#define THREADPOOL_INIT_COUNT 20
#define TASK_INIT_SIZE      1000
void task_entry(struct nTask *task) { //type 
  //struct nTask *task = (struct nTask*)task;
  int idx = *(int *)task->user_data;
  printf("idx: %d\n", idx);
  free(task->user_data);
  free(task);
}
int main(void) {
  ThreadPool pool = {0};
  nThreadPoolCreate(&pool, THREADPOOL_INIT_COUNT);
  // pool --> memset();
  int i = 0;
  for (i = 0;i < TASK_INIT_SIZE;i ++) {
    struct nTask *task = (struct nTask *)malloc(sizeof(struct nTask));
    if (task == NULL) {
      perror("malloc");
      exit(1);
    }
    memset(task, 0, sizeof(struct nTask));
    task->task_func = task_entry;
    task->user_data = malloc(sizeof(int));
    *(int*)task->user_data  = i;
    nThreadPoolPushTask(&pool, task);
  }
  getchar();//让程序停留在这一步,直到它从键盘接收到消息.
}
#endif

对于条件变量的理解

当线程wait的时候,通过条件变量,去告知那个阻塞的线程,现在可以不用阻塞了。

为什么在发送激活(广播或者信号)线程的时候要额外加锁呢?看下图就明白了


目录
打赏
0
0
0
0
7
分享
相关文章
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
19天前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
44 17
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
54 26
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
58 16
C++一分钟之-嵌入式编程与裸机开发
通过这些内容的详细介绍和实例解析,希望能帮助您深入理解C++在嵌入式编程与裸机开发中的应用,提高开发效率和代码质量。
62 13
那C++适合开发哪些项目
C++ 是一种功能强大、应用广泛的编程语言,适合开发多种类型的项目。它在游戏开发、操作系统、嵌入式系统、科学计算、金融、图形图像处理、数据库管理、网络通信、人工智能、虚拟现实、航空航天等领域都有广泛应用。C++ 以其高性能、内存管理和跨平台兼容性等优势,成为众多开发者的选择。
WK
180 1
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
1067 3
C++和Java哪个更适合开发移动应用
本文对比了C++和Java在移动应用开发中的优劣,从市场需求、学习难度、开发效率、跨平台性和应用领域等方面进行了详细分析。Java在Android开发中占据优势,而C++则适合对性能要求较高的场景。选择应根据具体需求和个人偏好综合考虑。
WK
108 0
WK
|
4月前
|
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
185 0
如何使用 C++ 开发 Redis 模块
如何使用 C++ 开发 Redis 模块
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等