08-系统编程(线程池)

线程池



一、线程池实现过程

1.线程池

线程池就是多个线程组合起来的一个集合,就好像一家公司,由多个员工组成的一个集合。当有任务时,这些线程就会去处理这些任务,当没有任务时,线程就会休息。

2.描述一个线程池-> 使用一个结构体

typedef struct thread_pool
{
    pthread_mutex_t lock;    -> 互斥锁
    pthread_cond_t  cond;    -> 条件变量
    bool shutdown;         -> 当前线程池的标志位  true -> 线程池关闭了,  false  -> 线程池没有关闭。
    struct task *task_list;  -> 任务队列的头节点
    pthread_t *tids;         -> 储存所有线程的ID号
    //unsigned max_waiting_tasks;  -> 最大等待任务的数目
    unsigned waiting_tasks;         -> 当前正在等待处理的任务的个数//链表中有多少个结点,每个结点就是一个任务,也就是当前有多少个任务
    unsigned active_threads;     -> 活跃的线程个数 //表示当前线程池里面有多少条线程
    
}thread_pool;

//任务队列 的结点数据类型---- 也就是说 任务结构体的数据类型
struct task
{
    /* 数据域 */
    void *(*do_task)(void *arg); //函数指针 -> 这个任务需要执行的函数,这个函数必须是: void *fun(void *arg)
    void *arg;    -> 传递给执行的函数的参数

    /* 指针域 */
    struct task *next;  -> 指向下一个任务节点的指针。
};

3. 初始化 -> init_pool()

bool init_pool(thread_pool *pool, unsigned int threads_number)
{
    //1. 初始化线程池互斥锁
    pthread_mutex_init(&pool->lock, NULL);
    
    //2. 初始化条件变量
    pthread_cond_init(&pool->cond, NULL);i

    //3. 初始化标志位为false,代表当前线程池正在运行。
    pool->shutdown = false;
    
    //4. 初始化任务队列的头节点
    pool->task_list = malloc(sizeof(struct task));
    
    //5. 为储存线程ID号申请空间。
    pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);

    //第4步与第5步错误判断
    if(pool->task_list == NULL || pool->tids == NULL)
    {
        perror("allocate memory error");
        return false; //初始化线程池失败
    }

    //6. 为线程池任务队列的头节点的指针域赋值NULL
    pool->task_list->next = NULL; //单向链表
    
    //7. 设置线程池最大任务个数为1000
    pool->max_waiting_tasks = MAX_WAITING_TASKS;
    
    //8. 当前需要处理的任务为0
    pool->waiting_tasks = 0;
    
    //9. 初始化线程池中线程的个数
    pool->active_threads = threads_number;

    //10. 创建线程
    int i;
    for(i=0; i<pool->active_threads; i++)
    {
        if(pthread_create(&((pool->tids)[i]), NULL,routine, (void *)pool) != 0)
        {
            perror("create threads error");
            return false;
        }
    }

    //11. 线程池初始化成功
    return true;
}

总结:初始化线程池函数其实就是给线程池的结构体赋值,将一些变量赋初值,以及初始化变量,为指针申请空间

4、添加任务 -> add_task()

bool add_task(thread_pool *pool,void *(*do_task)(void *arg), void *arg)
{
    //1. 为新任务的节点申请空间
    struct task *new_task = malloc(sizeof(struct task));
    if(new_task == NULL)
    {
        perror("allocate memory error");
        return false;
    }
    
    //2. 为新节点的数据域与指针域赋值
    new_task->do_task = do_task;  
    new_task->arg = arg; 
    new_task->next = NULL;  
    
    //3.  在添加任务之前,必须先上锁,因为添加任务属于访问临界资源 -> 任务队列
    pthread_mutex_lock(&pool->lock);
    
    //4. 如果当前需要处理的任务个数>=1000
    if(pool->waiting_tasks >= MAX_WAITING_TASKS)
    {
        //解锁
        pthread_mutex_unlock(&pool->lock);
        
        //打印一句话提示一下,太多任务了
        fprintf(stderr, "too many tasks.\n");
        
        //释放掉刚刚准备好的新节点
        free(new_task);

        return false;
    }
    
    //5. 寻找任务队列中的最后一个节点(尾插)
    struct task *tmp = pool->task_list;
    while(tmp->next != NULL)
        tmp = tmp->next;
    //从循环中出来时,tmp肯定是指向最后一个节点
    
    //6. 让最后一个节点的指针域指向新节点
    tmp->next = new_task;
    
    //7. 当前需要处理的任务+1
    pool->waiting_tasks++;

    //8. 添加完毕,就解锁。
    pthread_mutex_unlock(&pool->lock);

    //9. 唤醒条件变量中的一个线程起来做任务(单播)
    pthread_cond_signal(&pool->cond);
    
    return true;

总结: 插入一个新节点到任务队列的末尾,唤醒条件变量中的一个线程起来做任务

5.线程例程函数 -> void *routine(void *arg)

void *routine(void *arg)
{
    //1. 先接住线程池的地址
    thread_pool *pool = (thread_pool *)arg;
    struct task *p;

    while(1)
    {
        //2. 线程取消例程函数
        //将来要是有人要取消我,请先把锁解开,然后再响应取消。
        pthread_cleanup_push(handler, (void *)&pool->lock);
        
        //3. 上锁
        pthread_mutex_lock(&pool->lock);
        
        //如果当前线程池没有关闭,并且当前线程池没有任务做(有线程没有任务,池子是打开的)
        while(pool->waiting_tasks == 0 && !pool->shutdown)
        {
            //那么就进去条件变量中睡觉。
            pthread_cond_wait(&pool->cond, &pool->lock); //自动解锁
        }
        
        //要是线程池关闭了,或者有任务做,这些线程就会运行到这行代码
        //判断当前线程池是不是关闭了,并且没有等待的任务
        if(pool->waiting_tasks == 0 && pool->shutdown == true)
        {    
            //如果线程池关闭,又没有任务做
            //线程那么就会解锁
            pthread_mutex_unlock(&pool->lock);    
            
            //线程走人
            pthread_exit(NULL); 
        }
        
        //能运行到这里,说明有任务做  (删除节点)
        //p指向头节点的下一个
        p = pool->task_list->next;
        
        //让头节点的指针域指向p的下一个节点
        pool->task_list->next = p->next;
        
        //当前任务个数-1
        pool->waiting_tasks--;

        //解锁
        pthread_mutex_unlock(&pool->lock);
        
        //删除线程取消例程函数
        pthread_cleanup_pop(0);
        
        //设置线程不能响应取消
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 
        
        //执行这个p节点指向的节点的函数(执行任务)
        (p->do_task)(p->arg);  //等价跑了代码 eat(NULL)
        
        //设置线程能响应取消
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        //释放p对应的空间
        free(p);
    }

    pthread_exit(NULL);
}

总结:线程去任务队列中拿一个节点出来,就执行该任务(过程不能被取消),如果没有任务做,就去睡觉

6、添加线程 -> add_thread()

int add_thread(thread_pool *pool, unsigned additional_threads)
{
    //如果说你想添加0条,则直接返回0。
    if(additional_threads == 0)
        return 0; 

    unsigned total_threads = pool->active_threads + additional_threads;
             //total_threads = 原本 2条 + 现在再加2条
                        
    int i, actual_increment = 0;
    
    // i = 2 ; i<4 && i<20; i++
    for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS;  i++) 
    {
        if(pthread_create(&((pool->tids)[i]),NULL, routine, (void *)pool) != 0)
        {
            perror("add threads error");
            if(actual_increment == 0) 
                return -1;

            break;
        }
        actual_increment++;  //真正创建线程的条数
    }

    pool->active_threads += actual_increment; //当前线程池线程个数 = 原来的个数 + 实际创建的个数
    
    return actual_increment; //返回真正创建的个数
}

总结: 根据用户的需要去创建线程,但是不能超过20条

7、删除线程 -> remove_thread()

int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
    //1. 如果你想删0条,直接返回。
    if(removing_threads == 0)
        return pool->active_threads;  //返回当前剩余的线程数

    int remaining_threads = pool->active_threads - removing_threads;
    //            3         =            5         -  2
    //              0            =             5         -  5
    //           -3         =            5         -  8

    remaining_threads = remaining_threads > 0 ? remaining_threads : 1;

    int i;  
    for(i=pool->active_threads-1; i>remaining_threads-1; i--)
    {    
        errno = pthread_cancel(pool->tids[i]); //取消这些线程
        if(errno != 0)
            break;
    }

    if(i == pool->active_threads-1) //删除失败
        return -1;
    else
    {
        pool->active_threads = i+1; //当前实际线程个数
        return i+1; 
    }

总结: 根据用户需要,去取消掉一些线程,但是线程池中线程不能小于1条

8、销毁线程池-> destroy_pool()

bool destroy_pool(thread_pool *pool)
{
    //1. 设置线程池关闭标志为真
    pool->shutdown = true; 
    
    //广播
    pthread_cond_broadcast(&pool->cond);  //目的: 就是让线程退出!
    
    //2. 接合所有的线程
    int i;
    for(i=0; i<pool->active_threads; i++)
    {
        errno = pthread_join(pool->tids[i], NULL);  //阻塞等待所有的线程退出

        if(errno != 0)
        {
            printf("join tids[%d] error: %s\n",
                    i, strerror(errno));
        }
    
        else
            printf("[%u] is joined\n", (unsigned)pool->tids[i]);
        
    }

    //3. 释放一些空间
    free(pool->task_list); //释放链表
    free(pool->tids); //释放线程数
    free(pool);//释放线程池

    return true;
}

总结: 唤醒所有线程,让线程走人

总结

系统编程内容笔记至此完…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值