[Linux网络编程]线程池的封装(结构体方式)

      线程池在实际的服务器开发是非常重要的一环,他涉及的概念也比较多,例如线程的使用,互斥锁,条件变量,信号量的创建使用时机等等。同时你还要知道它如何自动销毁和创建,实现一个较为智能的模式。
      本文对线程池的一种构建方式进行详细分解解读注释,但也有许多需要改进的地方。
      完整的代码文中已经给出,如需整个测试项目,私信发。
目录链接
在这里插入图片描述

2021-09-08

  • 可以思考几个问题,为什么创建线程池,内部设计。
  • 可以想象成一个链表放包装好的函数叫做任务,许多独立的线程在等待(有时间限制,超过时间就退出线程了),不断判断有没有任务,如果有了就去取。有一个添加的函数,专门把任务挂起来,然后看看有没有空闲的线程,如果有啥事不用做,没有就创建线程,执行thread_routine这个不断取任务函数执行。当然也有第二种策略,线程不要超时等待退出和循环判断有没有任务,就一直wait阻塞等待,如果执行添加的函数,就去看看有没有空闲的线程,如果有就去唤醒一个线程执行任务。

1 为什么要epoll创建一个线程池

  1. 降低资源消耗。通过重复利用已创建的线程降低线程的创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程为稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

2 线程池的实现流程

  1. 创建一个线程池,初始化其中属性,可创建一定数量线程,放入队列,或者不初始化
  2. 线程都处于阻塞等待状态,不占用cpu,当超时等待,可以自己结束线程
  3. 当需要执行函数,则把函数包装成任务,并把任务传入空闲线程进行运行
  4. 当没有空闲进程且小于最大线程数要求,则创建线程执行任务
  5. 执行完任务的线程不需要退出,阻塞等待即可(或者设置等待时间)
  6. 若有线程池销毁通知,确保任务执行完退出销毁

3 准备工作,封装互斥锁和条件变量

      其实这边的封装就是为了方便使用,我们把信号量和互斥锁封装成一个结构体是有很大帮助的。
     在实际使用中我们都知道实现互斥访问(不懂可以参考生产者和消费者模型)其中一种方式就是条件变量和互斥锁的组合使用。
     多个线程可以理解成多个消费者。所以设计到很多共享资源,需要互斥锁。而条件变量是为了方便通知线程有可以执行的任务了。

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_ */

# condition.c

#include "condition.h"  

int condition_init(condition_t *cond)
{
   
	int status;
	if ((status = pthread_mutex_init(&cond->pmutex, NULL)))
		return status;

	if ((status = pthread_cond_init(&cond->pcond, NULL)))
		return status;

	return 0;
}

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)
{
   
	int status;
	if ((status = pthread_mutex_destroy(&cond->pmutex)))
		return status;

	if ((status = pthread_cond_destroy(&cond->pcond)))
		return status;

	return 0;
}

4 线程池的实现

4.1 结构说明(重)

线程池主要组成就三个部分

  • task_t类型的结构体
  • threadpool_t类型结构体
  • 四个实现函数

4.1.1 任务结构体

任务结构体简单的说就是对实际执行函数的封装

特别注意一下第一个成员变量,其实就是函数指针,函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。简单的说:理解成一个未初始化的函数变量

把函数指针和参数分开作为参数其实就是为了在调用pthread_create方便传入参数。第三个成员和链表的下一个指针域一个意思。我们这边吧任务串成链表。

typedef struct task
{
   
	// 任务回调函数
	// 简单的说就是返回值为空指针的函数指针
	void *(*run)(void *arg);	
	// 回调函数参数
	void *arg;					
	struct task *next;//指向的下一个结构体指针
} task_t;

4.1.2 线程池结构体

定义那么多关于线程的变量其实都是为了方便我们去管理线程。
唯一需要注意的是任务队列的头指针和尾指针。因为定义了这个,我们可以通过线程池对象成员很方便的放入或者取出任务。重要

typedef struct threadpool
{
   
	condition_t ready;		//初始化了一个条件变量和互斥锁
	task_t *first;			//任务队列头指针
	task_t *last;			//任务队列尾指针
	int counter;			//线程池中当前线程数
	int idle;				//线程池中当前正在
  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Windalove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值