Linux进程池与线程池以及线程池的简单实现

2023-05-16

通过动态创建子进程(或者子线程)来实现并发服务器的。这样做有如下缺点:

1、 动态创建进程(或线程)是比较耗费时间的,这将导致较慢的客户响应。

2、动态创建的子进程(或子线程)通常只用来为一个客户服务(除非我们做特殊处理),这将导致系统上产生大量的细微进程(或者线程)。进程(或者线程)间的切换消费大量CPU时间。

3、动态创建的子进程是当前进程的完整映像。当前进程必须谨慎地管理其分配的文件描述符和堆内存等系统资源,从而使系统的可用资源急剧下降,进而影响服务器的性能。

4、由于系统的资源有限,能够创建的子进程(或线程)的数量有限,所以响应客户端请求的数量有上限。

  • 进程池与线程池的概念
    进程池和线程池相似,所以下面对进程池的讨论完全适用于线程池。
    1、进程池是由服务器预先创建的一组子进程,这些子进程的数目在3-10个之间。httpd守护进程就是使用了包含7个子进程的进程池来实现并发的。线程池中的线程数量应该和CPU数量差不多。

    2、进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级,PGID等等。因为进程池在服务器启动之初就创建好了,所以每个子进程都相对“干净”,即他们没有打开不必要的文件描述符(从父进程继承而来),也不会错误地使用大块的堆内存(从父进程复制得到)。

    3、当有新的任务到来时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相对于动态创建子进程,选择一个已经存在的子进程的代价明显要小的多。

  • 子进程选择算法
    1、主进程使用某种算法主动选择子进程。最简单、最常用的算法是随机算法和Round-Robin(轮流选取)算法。

    2、主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程获得新任务的“接管权”,它可以从工作队列中取出并执行之,而其他子进程将继续睡眠在工作队列上。

    3、当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新进程需要处理,并传递必要的数据。最简单的方法是,在父进程和子进程之间预先建立好一条管道,然后通过该管道来实现所有进程间通信(当然,要预先定义好一套协议来规范管道的使用)。在父线程和子线程之间传递数据就要简单的多,因为我们可以把这些数据定义全局的,那么他们本身就是被所有线程共享的。

  • 线程池的简单实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
/*
*线程池里所有运行和等待的任务都是一个CThread_worker
*由于所有任务都在链表里,所以是一个链表结构
*/
typedef struct worker
{
    /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/
    void *(*process) (void *arg);
    void *arg;/*回调函数的参数*/
    struct worker *next;
} CThread_worker;

/*线程池结构*/
typedef struct
{
    pthread_mutex_t queue_lock;
    pthread_cond_t queue_ready;
    /*链表结构,线程池中所有等待任务*/
    CThread_worker *queue_head;
    /*是否销毁线程池*/
    int shutdown;
    pthread_t *threadid;
    /*线程池中允许的活动线程数目*/
    int max_thread_num;
    /*当前等待队列的任务数目*/
    int cur_queue_size;
} CThread_pool;

int pool_add_worker (void *(*process) (void *arg), void *arg);
void *thread_routine (void *arg);

static CThread_pool *pool = NULL;

void pool_init (int max_thread_num)
{
    pool = (CThread_pool *) malloc (sizeof (CThread_pool));
    pthread_mutex_init (&(pool->queue_lock), NULL);
    pthread_cond_init (&(pool->queue_ready), NULL);
    pool->queue_head = NULL;
    pool->max_thread_num = max_thread_num;
    pool->cur_queue_size = 0;
    pool->shutdown = 0;
    pool->threadid =
        (pthread_t *) malloc (max_thread_num * sizeof (pthread_t));
    int i = 0;
    for (i = 0; i < max_thread_num; i++)
    { 
        pthread_create (&(pool->threadid[i]), NULL, thread_routine,
                NULL);
    }
}

/*向线程池中加入任务*/
int pool_add_worker (void *(*process) (void *arg), void *arg)
{
    /*构造一个新任务*/
    CThread_worker *newworker =
        (CThread_worker *) malloc (sizeof (CThread_worker));
    newworker->process = process;
    newworker->arg = arg;
    newworker->next = NULL;/*别忘置空*/
    pthread_mutex_lock (&(pool->queue_lock));
    /*将任务加入到等待队列中*/
    CThread_worker *member = pool->queue_head;
    if (member != NULL)
    {
        while (member->next != NULL)
            member = member->next;
        member->next = newworker;
    }
    else
    {
        pool->queue_head = newworker;
    }
    assert (pool->queue_head != NULL);
    pool->cur_queue_size++;
    pthread_mutex_unlock (&(pool->queue_lock));
    /*好了,等待队列中有任务了,唤醒一个等待线程;
    注意如果所有线程都在忙碌,这句没有任何作用*/
    pthread_cond_signal (&(pool->queue_ready));
    return 0;
}

/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直
把任务运行完后再退出*/
int pool_destroy ()
{
    if (pool->shutdown)
        return -1;/*防止两次调用*/
    pool->shutdown = 1;
    /*唤醒所有等待线程,线程池要销毁了*/
    pthread_cond_broadcast (&(pool->queue_ready));
    /*阻塞等待线程退出,否则就成僵尸了*/
    int i;
    for (i = 0; i < pool->max_thread_num; i++)
        pthread_join (pool->threadid[i], NULL);
    free (pool->threadid);
    /*销毁等待队列*/
    CThread_worker *head = NULL;
    while (pool->queue_head != NULL)
    {
        head = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        free (head);
    }
    /*条件变量和互斥量也别忘了销毁*/
    pthread_mutex_destroy(&(pool->queue_lock));
    pthread_cond_destroy(&(pool->queue_ready));

    free (pool);
    /*销毁后指针置空是个好习惯*/
    pool=NULL;
    return 0;
}

void* thread_routine (void *arg)
{
    printf ("starting thread 0x%x/n", pthread_self ());
    while (1)
    {
        pthread_mutex_lock (&(pool->queue_lock));
        /*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意
        pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
        while (pool->cur_queue_size == 0 && !pool->shutdown)
        {
            printf ("thread 0x%x is waiting/n", pthread_self ());
            pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock));
        }
        /*线程池要销毁了*/
        if (pool->shutdown)
        {
            /*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/
            pthread_mutex_unlock (&(pool->queue_lock));
            printf ("thread 0x%x will exit/n", pthread_self ());
            pthread_exit (NULL);
        }
        printf ("thread 0x%x is starting to work/n", pthread_self ());
        /*assert是调试的好帮手*/
        assert (pool->cur_queue_size != 0);
        assert (pool->queue_head != NULL);

        /*等待队列长度减去1,并取出链表中的头元素*/
        pool->cur_queue_size--;
        CThread_worker *worker = pool->queue_head;
        pool->queue_head = worker->next;
        pthread_mutex_unlock (&(pool->queue_lock));
        /*调用回调函数,执行任务*/
        (*(worker->process)) (worker->arg);
        free (worker);
        worker = NULL;
    }
    /*这一句应该是不可达的*/
    pthread_exit (NULL);
}

void* myprocess (void *arg)
{
    printf ("threadid is 0x%x, working on task %d/n", pthread_self (),*(int *) arg);
    sleep (1);/*休息一秒,延长任务的执行时间*/
    return NULL;
}
int main ()
{
    pool_init (3);/*线程池中最多三个活动线程*/

    /*连续向池中投入10个任务*/
    int *workingnum = (int *) malloc (sizeof (int) * 10);
    int i;
    for (i = 0; i < 10; i++)
    {
        workingnum[i] = i;
        pool_add_worker (myprocess, &workingnum[i]);
    }
    /*等待所有任务完成*/
    sleep (5);
    /*销毁线程池*/
    pool_destroy ();
    free (workingnum);
    return 0;
}

结果截图:
这里写图片描述

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

Linux进程池与线程池以及线程池的简单实现 的相关文章

随机推荐

  • 无人机六旋翼数学建模[matlab-simulink]

    写在前面 xff0c 这篇文章是借鉴Drexel University 的Senior Design project的matlab simulink四旋翼模型 xff0c 在此基础上针对六旋翼进行的基本改进 xff0c 这里只对 43 型模
  • stm32连接DHT11温湿度传感器

    目录 1 DHT11简介 1 1 连接电路 1 2 串行接口 单线双向 2 cubeMX设置 3 代码开发 3 1 实现定时函数 3 2 打开串口调试 3 4 测试代码实现 4 运行效果 1 DHT11简介 1 1 连接电路 信息如下 xf
  • STM32CubeMX 真的不要太好用

    STM32CubeMX 真的不要太好用 由于工作内容的变动 xff0c 我已经很久没有正经的玩过单片机了 xff0c 近期又要用它做个小玩意了 xff0c 还是选 stm32 吧 xff0c 外设库开发不要太方便 xff0c 哈哈哈 先去
  • ESP-Drone控制板设计的第二个任务-绘制USB-TTL串口下载电路和ESP32-S2芯片内置USB接口电路

    1 摘要 ESP32系列处理器一般会需要采用串口来下载代码 xff0c 因此在其设计中都会保留一个USB TTL串口电路 xff0c 查看乐鑫官网的参考设计 xff0c 基本上是采用CP2102这颗USB转TTL串口芯片 xff0c 但在本
  • Airflow ETL任务调度工具 介绍

    Airflow 是 Apache 基金会的一套用于创建 管理和监控工作流程的开源平台 xff0c 是一套非常优秀的任务调度工具 截至2022年7月 xff0c 在GitHub上已经拥有近27k的star 本文主要介绍一下Airflow 2
  • Data_web(八)mysql增量同步到mongodb

    1 mongdb连接 连接方式如下 xff08 重要 xff01 xff01 xff01 xff01 xff0c 账号密码必须建立在db下面 xff0c 如果默认再admin下面 xff0c 导致无法切换库 xff0c 连接报错 xff09
  • 2021电子设计竞赛飞控视觉之openmv寻找方格中心

    写在前面 这是我在电赛飞控备赛期间写的一个小函数 xff0c 功能是寻找目标点所在方格的中心 这样四旋翼在方格地图上移动一定距离之后就可以使用openmv将四旋翼辅助定位至目前所在方格的中心 今年G题刚出来的时候本来以为能用上这个函数进行辅
  • centos6.5vim基本配置

    简单的vim配置 xff1a 在目录 etc 下面 xff0c 有个名为vimrc的文件 xff0c 这是系统中公共的vim配置文件 xff0c 对所有用户都有效 而在每个用户的主目录下 xff0c 都可以自己建立私有的配置文件 xff0c
  • atexit注册函数

    函数名 atexit 头文件 include lt stdlib h gt 功 能 注册终止函数 即main执行结束后调用的函数 用 法 int atexit void func void 注意 xff1a 按照ISO C的规定 xff0c
  • Linux管道的容量大小及管道的数据结构

    一 管道容量 xff1a 我们通过ulimit a 命令查看到的pipo size定义的是内核管道缓冲区的大小 xff0c 这个值的大小是由内核设定的 xff1b 而pipe capacity指的是管道的最大值 xff0c 即容量 xff0
  • 线程初体验

    线程的概念 xff1a 线程是一个进程地址空间的一个控制流程 xff0c 是调度的基本单位 xff0c 由于同一进程的多个线程共享同一地址空间 因此Text Segment Data Segment都是共享的 如果定义一个函数 在各线程中都
  • 死锁的四个必要条件

    死锁产生的四个必要条件 互斥条件 xff1a 资源是独占的且排他使用 xff0c 进程互斥使用资源 xff0c 即任意时刻一个资源只能给一个进程使用 xff0c 其他进程若申请一个资源 xff0c 而该资源被另一进程占有时 xff0c 则申
  • 线程安全与可重入函数的区别

    线程安全 xff1a 一般来讲就是一个代码块被多个并发线程反复调用时会一直产生正确的结果 如何确保线程安全 xff1a 确保线程安全 主要 考虑线程之间共享变量的安全 xff0c 每个线程私有的内容包括 xff1a 线程id xff0c e
  • Linux模拟实现sleep

    工作原理 linux中的sleep函数能够让程序休眠一定的秒数 xff0c 到时间后自动恢复运行 实现思路 设定睡眠的秒数 睡眠 xff08 挂起 xff09 恢复运行实现机制 设定睡眠的秒数 xff1a 采用alarm 函数设定需要睡眠的
  • 基于ESP32C3处理器创建Hello World工程-并使用OpenOCD进行Debug

    1 编程环境 1 1 硬件 序号 名称 描述 备注 1 ESP C3 12F KIT 深圳安信可开发的基于其自家ESP C3 12F模块的开发板 淘宝购买 2 ESP Prog 乐鑫官方推出基于FT2232HL接口芯片的JTAG调试器 淘宝
  • 平衡二叉树旋转详解

    平衡二叉树的定义 xff08 AVL xff09 定义 平衡二叉树或者是一棵空树 xff0c 或者满足以下的性质 xff1a 它的左子树和右子树的高度之差的绝对值不超过1 xff0c 并且左子树和右子树也是一个平衡二叉树 平衡因子 左子树高
  • Linux进程组,作业,会话,作业控制详解

    进程组 xff08 1 xff09 每个进程除了有一个进程id之外还属于进程组 xff0c 进程组是一个或者多个进程的集合 xff0c 通常 xff0c 他们与同一作业相关联 xff0c 可以接收来自同一终端的各种信号 xff08 2 xf
  • 如何写一个linux精灵进程

    什么是精灵进程 精灵进程也称守护进程 xff08 Daemon xff09 xff1a 是运行在后台的一种特殊进程 xff0c 它独立于控制终端并周期性的执行某种任务 xff0c 或等待处理某些发生的事件 Linux大多数服务器就是用精灵进
  • TCP的四种定时器

    TCP使用的四种定时器 xff08 Timer xff09 重传计时器 xff08 Retransmission Timer xff09 坚持计时器 xff08 Persistent Timer xff09 保活计时器 xff08 keep
  • Linux进程池与线程池以及线程池的简单实现

    通过动态创建子进程 xff08 或者子线程 xff09 来实现并发服务器的 这样做有如下缺点 xff1a 1 动态创建进程 xff08 或线程 xff09 是比较耗费时间的 xff0c 这将导致较慢的客户响应 2 动态创建的子进程 xff0