转眼间离15年的春节越来越近了,还有两周的工作时间貌似心已经不在异乡了,期待与家人团聚的日子,当然最后两周也得坚持站好最后一班岗,另外期待的日子往往是心里不能平静的,越是想着过年,反而日子过得越慢,于是乎,还是用学习来充斥这些碎片时间,当人一充实,时间也就过得快了,继续学习:

上次中已经用互斥锁与条件变量来改造了生产者与消费者问题,这次利用它来实现一个线程池,加强对条件变量及互斥锁的认识,下面开始:

关于什么是线程池,这里就不多说了,应该基本都在实际中用到过,下面关于线程池实现有几个点需要说明一下:

线程池,顾名思义就是拥有若干个线程,对于线程的个数是有严格的要求的,并非越多越好,太多了会增加系统的开销,太少了又会降低并发量。

执行时间较长的任务是不太适合放在线程池中进行处理,比如说:线程的执行时间跟进程的生命周期是一致的,那么这个任务的执行就没必要放到线程池中进行,直接用普通的线程既可。

那线程池当中的线程个数究竟存放多少个比较合适呢?实际上这跟任务类型有关系:

①、计算密集型任务:一般这个任务是占用CPU的,它很少被外界的事件打断,这时线程个数 = CPU个数,如果线程个数>CPU个数,由于CPU的个数是一定的,那么能够并发的数目也是一定的,所以会用少量的CPU个数来调度多个线程,这肯定会涉及到线程与线程之间的切换开销,因而会降低效率。

②、I/O密集型任务:这种任务在运行时,可能会被I/O中断,也就是说这个线程会挂起,这时线程个数 > CPU个数

那接下来先了解一下线程池实现中,需要用到的结构体:

下面则开始实现,首先在头文件中定义上面的数据结构:

其中用到了条件变量,这里对条件变量进行了简单的封装,所以先来看下是如何封装的:

从声明的这些函数中很容易就能想到它们的各个实现:

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 ;
} //对互斥锁进行锁定
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 ;
}

接着来实现一下线程池:

在正式实现这些函数之前,其实可以先从使用者的角度来看,如何使用这些线程池,如下:

其实这是典型的“测试驱动开发”,先编写好测试代码,然后再来从使用的角度去具体实现,下面则开始具体实现线程池相应的方法:

接下来实现往线程池中添加任务:

其添加过程是从尾部进行添加的,其实就是单链表的应用。

这里需要注意一个问题,就是在使用条件变量之前是需要对进行互斥的,因为队列资源是生产者与消费者都可以访问的,所以需要互斥:

接下来来处理线程的执行入口函数,线程应该是等待任务并且处理任务,也就是它是一个消费者线程:

下面来编译运行一下,在运行之后,需要在main函数中做一下sleep:

而分析一下输出结果:

而且是经过15秒之后,则进程退出了,但是有个问题,就是当任务执行完了,应该线程也能动态减少,目前当任务执行完了之后,所有线程都还在,也就是需要看到这样的输出:

但是目前看不到这样的状态,而是等到进程退出来线程才销毁,所以需要对代码进行改进,这时就需要用到等待超时的一个函数:

也就是如果线程等待超时了,则代表没有任务了,则该线程就可以销毁了,所以将condition_wait需要换成condition_timedwait函数:

查看一相man帮助:

下面来改造一下:

【说明】:获取当前时间可以用函数clock_gettime:

下面再来做超时处理:

好了下面再来运行一下:

从结果中可以看出:

接下来就剩最后一个销毁方法没有实现了,而main中的sleep则可以去掉了:

其中看到有等待条件变量,那是谁来通知该条件变量呢,当然是在任务执行时,于时需要修改任务执行线程里面的代码:

下面再来编译运行一下,在运行之前,可以将之前的休眠代码去掉了:

下面再来看下最终效果:

可见当线程任务都执行完了,所有的线程也销毁了,这样代码就是比较合理的,好了今天的逻辑有些复杂,需好好消化。

linux网络编程之简单的线程池实现的更多相关文章

  1. linux网络编程-一个简单的线程池(41)

    有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池 1.线程池拥有若干个线程,是线程的集合,线程池中的线程数目有严格的要求,用于执行大量的相对短 ...

  2. Linux C 实现一个简单的线程池

    线程池的定义 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如 ...

  3. Linux网络编程:一个简单的正向代理服务器的实现

    Linux是一个可靠性非常高的操作系统,但是所有用过Linux的朋友都会感觉到, Linux和Windows这样的"傻瓜"操作系统(这里丝毫没有贬低Windows的意思,相反这应该 ...

  4. Linux网络编程简单示例

    linux 网络编程是通过socket(套接字)接口实现,Socket是一种文件描述符,socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭& ...

  5. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  6. Linux 网络编程的5种IO模型:异步IO模型

    Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...

  7. 【linux草鞋应用编程系列】_5_ Linux网络编程

    一.网络通信简介   第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章.   二.linux网络通信     在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网 ...

  8. linux网络编程基础--(转自网络)

    转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...

  9. Linux网络编程学习路线

    转载自:https://blog.csdn.net/lianghe_work/article 一.网络应用层编程   1.Linux网络编程01——网络协议入门 2.Linux网络编程02——无连接和 ...

随机推荐

  1. 洛谷 题解 P1041 【传染病控制】

    [思路] 题目给出一棵树.第\(i\)步拆的一定是第\(i\)层与第\(i+1\)层之间的连边,否则不是最优(自行证明即可),所以可以暴力枚举每一次拆哪一个节点与上一个节点的连边. 把所有节点所在的层 ...

  2. [转帖]Linux内核系统体系概述

    Linux内核系统体系概述 https://www.cnblogs.com/alantu2018/p/8447369.html Linux 内核主要由 5 个模块构成,它们分别是: 进程调度模块 用来 ...

  3. [转帖]linux操作系统测试工具

    linux操作系统测试工具 http://cfdtesting.com/879156.html 作者: minions_222      来源: CFDTesting.com采编      发布于:  ...

  4. Quartz.Net—TriggerBuilder

    TriggerBuilder TriggerBuilder是一个建造者模式,链式建造.通过静态方法构建一个TriggerBuilder实例,然后再调用类方法Build()创建一个ITrigger的实现 ...

  5. 用Gson实现json与对象、list集合之间的相互转化

    先写一个Person实体类,导入Gson包 String jsonData="{\"userid\":\"1881140130\"}";// ...

  6. hadoop常见问题收集

    hadoop 搭建 常用命令记录 快捷键安装在/user/local/bin目录下 nano 文件名 ctrl + k 剪切一行 ctrl + o 保存并重命名,不重命名直接enter ctrl + ...

  7. CF731E Funny Game

    题目描述 一个长度为 N 的序列 ai ,双方轮流操作 每次的操作是选择一个长度大于 1 的前缀,计算它的和 s ,然后 用 s 替换它的前缀,同时当前玩家获得 s 的分数. 当只剩下一个元素,游戏结 ...

  8. Visual Studio快速入门(大纲)

    安装与配置 下载 配置Visual Studio环境支持C++桌面编程 Hello World 支持C++98 ( Hello World) 支持C++17( Hello World) 配置Visua ...

  9. 转:数据库实例自动crash并报ORA-27157、ORA-27300等错误

    rhel7.2上安装12C RAC数据库后,其中一个数据库实例经常会自动crash.查看alert日志发现以下错误信息: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Errors ...

  10. docker学习笔记(一)--介绍和基本组成

    Docker基本介绍 1.什么是docker docker本身不是容器,是创建容器的工具,是应用容器引擎,将应用程序自动部署到容器的开源引擎. 2.docker的目标特点 简单轻量,快速开发,具备可移 ...