python是如何实现进程池和线程池的_进程、线程、线程池和协程如何理解?
1、进程、线程、线程池的概念
进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
线程,程序执行流的最小执行单位,是进程中的实际运作单位。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗,那为什么我们要使用线程池,主要解决如下几个问题:
创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率
线程并发数量过多,抢占系统资源,从而导致系统阻塞
能够容易的管理线程,比如:线程延迟执行、执行策略等
2、线程的 生命周期
线程的生命周期,线程的生命周期可以利用以下的图解来更好的理解:
首先使用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。而我们在图解中可以看出,线程还具有一个则色的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程。
文末超强干货分享
3、单线程和多线程概念
单线程,顾名思义即是只有一个线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解
多线程,创建多个线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。
并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?
并发:从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情
并行:则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础上完成。
还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,共享资源还来不及发生变化,就同时有两个线程使用了同一条资源,会造成数据不一致性,导致线程执行错误发生。
有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程占用了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。
4、JAVA中线程池的实现
在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类,学习Java中的线程池,就可以直接学习它。对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置,既然这些参数这么重要,就来看看构造函数的各个参数吧
ThreadPoolExecutor提供了四个构造函数
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
其它这四个构造函数,一共牵涉到7个参数类型,下面主要讲解七个参数。
- int corePoolSize => 该线程池中核心线程数最大值
核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉很好理解吧,正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了
int maximumPoolSize
该线程池中线程总数最大值线程总数 = 核心线程数 + 非核心线程数。核心线程在上面解释过了,这里说下非核心线程:不是核心线程的线程(别激动,把刀放下…),其实在上面解释过了
long keepAliveTime
该线程池中非核心线程闲置超时时长一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉如果设置allowCoreThreadTimeOut = true,则会作用于核心线程
TimeUnit unit
keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
BlockingQueue workQueue
该线程池中的任务队列:维护着等待执行的Runnable对象当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务常用的workQueue类型:SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSizeArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
ThreadFactory threadFactory
创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。小伙伴应该知道AsyncTask是对线程池的封装吧?那就直接放一个AsyncTask新建线程池的threadFactory参数源码吧:new ThreadFactory() {private final AtomicInteger mCount = new AtomicInteger(1);public Thread new Thread(Runnable r) {return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());}
}
RejectedExecutionHandler handler
这玩意儿就是抛出异常专用的,比如上面提到的两个错误发生了,就会由这个handler抛出异常,你不指定他也有个默认的抛异常能抛出什么花样来?一般情况下根本用不上。
新建一个线程池的时候,一般只用5个参数的构造函数。
向ThreadPoolExecutor添加任务
那说了这么多,你可能有疑惑,我知道new一个ThreadPoolExecutor,大概知道各个参数是干嘛的,可是我new完了,怎么向线程池提交一个要执行的任务啊?
通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加一个任务
ThreadPoolExecutor的策略
上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:
线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
线程数量达到了corePools,则将任务移入队列等待
队列已满,新建线程(非核心线程)执行任务
队列已满,总线程数又达到了maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常
5、常见四种线程池
如果你不想自己写一个线程池,那么你可以从下面看看有没有符合你要求的(一般都够用了),如果有,那么很好你直接用就行了,如果没有,那你就老老实实自己去写一个吧
Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的,下面我都会贴出这四种线程池构造函数的源码,各位大佬们一看便知!
来,走起:
CachedThreadPool()
可缓存线程池:
线程数无限制(没有核心线程,全部是非核心线程)
有空闲线程则复用空闲线程,若无空闲线程则新建线程
一定程序减少频繁创建/销毁线程,减少系统开销
适用场景:适用于耗时少,任务量大的情况
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
源码:
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
FixedThreadPool()
定长线程池:
- 有核心线程,核心线程数就是线程的最大数量(没有非核心线程)
- 可控制线程最大并发数(同时执行的线程数)
- 超出的线程会在队列中等待
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);//threadFactory => 创建线程的方法!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮
ScheduledThreadPool()
定长线程池:
支持定时及周期性任务执行。
有核心线程,也有非核心线程
非核心线程数量为无限大
适用场景:适用于执行周期性任务
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());
}
SingleThreadExecutor()
单线程化的线程池:
有且仅有一个工作线程执行任务
所有任务按照指定顺序执行,即遵循队列的入队出队规则
适用场景:适用于有顺序的任务应用场景
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
源码:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
还有一个Executors.newSingleThreadScheduledExecutor()
结合了3和4,就不介绍了,基本不用。
6、什么是协程?
问题:协程存在的原因?协程能够解决哪些问题?
在我们现在CS,BS开发模式下,服务器的吞吐量是一个很重要的参数。其实吞吐量是IO处理时间加上业务处理。为了简单起见,比如,客户端与服务器之间是长连接的,客户端定期给服务器发送心跳包数据。客户端发送一次心跳包到服务器,服务器更新该新客户端状态的。心跳包发送的过程,业务处理时长等于IO读取(RECV系统调用)加上业务处理(更新客户状态)。吞吐量等于1s业务处理次数。
业务处理(更新客户端状态)时间,业务不一样的,处理时间不一样,我们就不做讨论。
那如何提升recv的性能。若只有一个客户端,recv的性能也没有必要提升,也不能提升。若在有百万计的客户端长连接的情况,我们该如何提升。以Linux为例,在这里需要介绍一个“网红”就是epoll。服务器使用epoll管理百万计的客户端长连接,代码框架如下:
hile (1) {int nready = epoll_wait(epfd, events, EVENT_SIZE, -1);for (i = 0;i < nready;i ++) {int sockfd = events[i].data.fd;if (sockfd == listenfd) {int connfd = accept(listenfd, xxx, xxxx);setnonblock(connfd);ev.events = EPOLLIN | EPOLLET;ev.data.fd = connfd;epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);} else {handle(sockfd);}}
}
对于响应式服务器,所有的客户端的操作驱动都是来源于这个大循环。来源于epoll_wait的反馈结果。
对于服务器处理百万计的IO。Handle(sockfd)实现方式有两种。
第一种,handle(sockfd)函数内部对sockfd进行读写动作。代码如下
int handle(int sockfd) {recv(sockfd, rbuffer, length, 0);parser_proto(rbuffer, length);send(sockfd, sbuffer, length, 0);}
handle的io操作(send,recv)与epoll_wait是在同一个处理流程里面的。这就是IO同步操作。
优点:
1. sockfd管理方便。
2. 操作逻辑清晰。
缺点:
1. 服务器程序依赖epoll_wait的循环响应速度慢。
2. 程序性能差
第二种,handle(sockfd)函数内部将sockfd的操作,push到线程池中,代码如下:
int thread_cb(int sockfd) {// 此函数是在线程池创建的线程中运行。// 与handle不在一个线程上下文中运行recv(sockfd, rbuffer, length, 0);parser_proto(rbuffer, length);send(sockfd, sbuffer, length, 0);
}int handle(int sockfd) {//此函数在主线程 main_thread 中运行//在此处之前,确保线程池已经启动。push_thread(sockfd, thread_cb); //将sockfd放到其他线程中运行。
}
Handle函数是将sockfd处理方式放到另一个已经其他的线程中运行,如此做法,将io操作(recv,send)与epoll_wait 不在一个处理流程里面,使得io操作(recv,send)与epoll_wait实现解耦。这就叫做IO异步操作。
优点:
1. 子模块好规划。
2. 程序性能高。
缺点:
正因为子模块好规划,使得模块之间的sockfd的管理异常麻烦。每一个子线程都需要管理好sockfd,避免在IO操作的时候,sockfd出现关闭或其他异常。
上文有提到IO同步操作,程序响应慢,IO异步操作,程序响应快。
下面来对比一下IO同步操作与IO异步操作。
代码如下:
https://github.com/wangbojing/c1000k_test/blob/master/server_mulport_epoll.c
在这份代码的486行,#if 1, 打开的时候,为IO异步操作。关闭的时候,为IO同步操作。
接下来把我测试接入量的结果粘贴出来。
IO异步操作,每1000个连接接入的服务器响应时间(900ms左右)。
IO同步操作,每1000个连接接入的服务器响应时间(6500ms左右)。
IO异步操作与IO同步操作
有没有一种方式,有异步性能,同步的代码逻辑。来方便编程人员对IO操作的组件呢? 有,采用一种轻量级的协程来实现。在每次send或者recv之前进行切换,再由调度器来处理epoll_wait的流程。
就是采用了基于这样的思考,写了NtyCo,实现了一个IO异步操作与协程结合的组件。https://github.com/wangbojing/NtyCo,
线程、进程了解懂的人应该不少,但是什么是协程,纯C写的协程框架有了解过吗?
不急,扫一扫
https://m.ke.qq.com/course/2705727?flowToken=1023499 (二维码自动识别)
为你解密协程以下的内容:
协程框架实现,调度器模式实现,底层原理,多核模式,性能分析,ntyco作者亲讲
协程起源 — 存在的原因?协程能够解决哪些问题?
协程案例 — 如何使用?与线程使用有何区别?
协程实现之工作流程 — 内部是如何工作的?
协程实现之原语操作 — 原语操作有哪些?分别如何实现?
协程实现之切换 — 上下文如何切换?代码如何实现?
协程实现之定义 — 运行体如何定义?调度器如何定义?
协程实现之调度器 — 协程如何被调度?
协程多核模式 — 多核实现
协程性能测试 — 实战性能测试
就等你的加入!
python是如何实现进程池和线程池的_进程、线程、线程池和协程如何理解?相关推荐
- 并发编程概念、程序线程进程、线程同步、互斥量、读写锁、协程并发
多线程: 多线程就是同时执行多个应用程序,需要硬件的支持 同时执行:不是某个时间段同时,cpu切换的比较快,所有用户会感觉是在同时运行 并发与并行: 并行(parallel):指在同一时刻,有多条指令 ...
- python中协程的理解_python协程的理解
一.介绍 什么是并发? 并发的本质就是切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制): 1.任务发生阻塞 2.计算任务时间过长,需要让出cpu给高 ...
- python线程进程协程面试_Python学习经验之谈:关于协程的理解和其相关面试问题...
都知道Python非常适合初学者学习来入门编程,昨天有伙伴留言说面试了Python岗位,问及了一个关于协程的问题,想了想还是跟大家出一篇协程相关的文章和在Python面试中可能会问及的相关面试问题.都 ...
- 进程、线程和协程的理解
转自: http://blog.csdn.net/guyan0319 https://blog.csdn.net/guyan0319/article/details/79411626 一.进程 进程 ...
- burp爆破线程设置多少_你知道线程池创建多少线程比较合理吗?
为什么会使用多线程 创建多少线程比较合适 结束语 <Java 2019 超神之路> <Dubbo 实现原理与源码解析 -- 精品合集> <Spring 实现原理与源码解析 ...
- java 线程亲缘性_亲缘性线程池,这是什么鬼?
一.前言 JDK中的线程池主要解决两个问题: 一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时,每当需要执行异步任务时候是直接 new一线程运行,而线程的创建和销毁是需要开销的 ...
- Java线程池状态判断源码_深入浅出Java线程池:源码篇
前言 在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用.(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适).本文则深入线程池的源码,主要是介绍Thr ...
- java线程切换速度_为什么说线程太多,cpu切换线程会浪费很多时间?
cpu在执行代码的时候[以下说明只在linux平台上,win我不会] 该程序已经是ELF executable file 且该文件内部按ELF格式存储了机器指令+数据 同时该文件必须引用linux 的 ...
- java线程卡住排查_基于 Java 线程栈 排查问题
除日志外,还有没有别的方式跟踪线上服务问题呢?或者,跟踪并排除日志里无法发现的问题? 方法当然是有的,就是通过现场快照定位并发现问题.我们所说的现场,主要指这两方面: Java 线程栈.线程栈是Jav ...
最新文章
- python学习第十节(yield表达式的应用+内置函数)
- sql Escape用法
- Springboot注册Filter
- angular ng-zorro 用组件自身方的法来重置表单校验
- 将php源码修改成存在注入的源码,天融信关于ucms系统存在代码注入漏洞的分析...
- 流媒体技术的国内外动态
- 第 1-3 课:深入理解字符串 + 面试题
- 高级GIT教程-如何使用GIT调试代码?
- linux下查看日志基本命令
- C#静态类,静态构造函数,静态变量
- JPA(五):映射关联关系------映射单向多对一的关联关系
- 将指定文件中的空格或换行删除(可选是否创建一个新文件)
- spring源代码分析
- Win10+Pytorch0.4.1版本+cuda一键安装
- OneNote的同步问题
- 宋浩《概率论与数理统计》自用笔记
- Chrome 去广告插件'Adblock Plus' ,以及插件安装失败方法
- Mardown、LaTex编辑器推荐
- css3 3d头像,创意css3头像展示_觉唯设计
- 招到不能胜任工作岗位的人,责任在不在HR?
热门文章
- 为什么刹车热了会失灵_网曝比亚迪汉“刹车失灵”,比亚迪称:是IPB模块仪表显示问题...
- VHDL实现简易停车场系统设计
- java反射可以获得什么属性_java反射获得属性的值
- c语言多线程 视频教程,如何用C语言实现多线程
- h5应用数据加密_邦伲德H5场景个性化开发,打破传统营销局限
- Mac OS上用item2连接CentOS7
- 设计用例测试下面的python程序def do_你能变形python的unittest测试用例吗?
- linux 6.8 安装教程,CentOS 6.8 7.2 安装图解详细教程
- ROS-URDF仿真
- 【JZOJ4964】【GDKOI2017模拟1.21】Rhyme