什么是线程池,如何使用,为什么要用

什么是线程池,如何使用,为什么要用,第1张

什么是线程池:  javautilconcurrentExecutors提供了一个 javautilconcurrentExecutor接口的实现用于创建线程池。多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。   

假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

线程池的作用:

线程池作用就是限制系统中执行线程的数量。

根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

线程池组成

一个线程池包括以下四个基本组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

常见线程池

①newSingleThreadExecutor

单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务

②newFixedThreadExecutor(n)

固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行

③newCacheThreadExecutor(推荐使用)

可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

④newScheduleThreadExecutor

大小无限制的线程池,支持定时和周期性的执行线程

线程池作用

线程池作用就是限制系统中执行线程的数量。

使用线程池有如下作用:

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

原文链接:

最近在做一个爬虫相关的项目,单线程的整站爬虫,耗时真的不是一般的巨大,运行一次也是心累,,,所以,要想实现整站爬虫,多线程是不可避免的,那么python多线程又应该怎样实现呢?这里主要要几个问题(关于python多线程的GIL问题就不再说了,网上太多了)。

一、 既然多线程可以缩短程序运行时间,那么,是不是线程数量越多越好呢?

显然,并不是,每一个线程的从生成到消亡也是需要时间和资源的,太多的线程会占用过多的系统资源(内存开销,cpu开销),而且生成太多的线程时间也是可观的,很可能会得不偿失,这里给出一个最佳线程数量的计算方式:

最佳线程数的获取:

1、通过用户慢慢递增来进行性能压测,观察QPS(即每秒的响应请求数,也即是最大吞吐能力。),响应时间

2、根据公式计算:服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) cpu数量

3、单用户压测,查看CPU的消耗,然后直接乘以百分比,再进行压测,一般这个值的附近应该就是最佳线程数量。

二、为什么要使用线程池?

对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初始只有一个链接a,那么,这个时候只启动一个线程,运行之后,得到这个链接对应页面上的b,c,d,,,等等新的链接,作为新任务,这个时候,就要为这些新的链接生成新的线程,线程数量暴涨。在之后的运行中,线程数量还会不停的增加,完全无法控制。所以,对于任务数量不端增加的程序,固定线程数量的线程池是必要的。

三、如何使用线程池

过去使用threadpool模块,现在一般使用concurrentfutures模块,这个模块是python3中自带的模块,但是,python27以上版本也可以安装使用,具体使用方式如下:

注意到:

concurrentfuturesThreadPoolExecutor,在提交任务的时候,有两种方式,一种是submit()函数,另一种是map()函数,两者的主要区别在于:

一:newCachedThreadPool

(1)缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse,如果没有,就建立一个新的线程加入池中;

(2)缓存型池子,通常用于执行一些生存周期很短的异步型任务;因此一些面向连接的daemon型server中用得不多;

(3)能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。

(4)注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止

二:newFixedThreadPool

(1)newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程

(2)其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子

(3)和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器

(4)从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:

fixed池线程数固定,并且是0秒IDLE(无IDLE)

cache池线程数支持0-IntegerMAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE

三:ScheduledThreadPool

(1)调度型线程池

(2)这个池子里的线程可以按schedule依次delay执行,或周期执行

四:SingleThreadExecutor

(1)单例线程,任意时间池中只能有一个线程

(2)用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)

多线程是为了能够让计算机资源合理的分配,对于处理不同的任务创建不同的线程进行处理,但是计算机创建一个线程或者销毁一个线程所花费的也是比较昂贵的,有时候需要同时处理的事情比较多,就需要我们频繁的进行线程的创建和销毁,这样花费的时间也是比较多的。为了解决这一问题,我们就可以引用线程池的概念。

所谓线程池就是将线程集中管理起来,当需要线程的时候,可以从线程池中获取空闲的线程,这样可以减少线程的频繁创建与销毁,节省很大的时间和减少很多不必要的操作。

在java中提供了ThreadPoolExecutor类来进行线程的管理,这个类继承于AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口,我们可以使用ThreadPoolExecutor来进行线程池的创建。

在ThreadPoolExecutor的构造方法中,有多个参数,可以配置不同的参数来进行优化。这个类的源码构造方法为:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)其中每个参数代表的意义分别为

corePoolSize : 线程池中的核心线程数量,当线程池中当前的线程数小于这个配置的时候,如果有一个新的任务到来,即使线程池中还存在空闲状态的线程,程序也会继续创建一个新的线程放进线程池当中

maximumPoolSize: 线程池中的线程最大数量

keepAliveTime:当线程池中的线程数量大于配置的核心线程数量(corePoolSize)的时候,如果当前有空闲的线程,则当这个空闲线程可以存在的时间,如果在keepAliveTime这个时间点内没有新的任务使用这个线程,那么这个线程将会结束,核心线程不会结束,但是如果配置了allowCoreThreadTimeOut = true,则当空闲时间超过keepAliveTime之后,线程也会被结束调,默认allowCoreThreadTimeOut = false,即表示默认情况下,核心线程会一直存在于线程池当中。

unit : 空闲线程保持连接时间(keepAliveTime)的时间单位

workQueue:阻塞的任务队列,用来保存等待需要执行的任务。

threadFactory :线程工厂,可以根据自己的需求去创建线程的对象,设置线程的名称,优先级等属性信息。

handler:当线程池中存在的线程数超过设置的最大值之后,新的任务就会被拒绝,可以自己定义一个拒绝的策略,当新任务被拒绝之后,就会使用hander方法进行处理。

在java中也提供了Executors工具类,在这个工具类中提供了多个创建线程池的静态方法,其中包含newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor等。但是他们每个方法都是创建了ThreadPoolExecutor对象,不同的是,每个对象的初始 参数值不一样;

socket接受线程:C语言为了高并发所以选择了epoll。当程序启动的时候(g_net_updatec文件中main函数,会启动一个thread见函数create_accept_task)这个thread就处理一件事情,只管接收客户端的连接,当有连接进来的时候 通过epoll_ctl函数,把socket fd 加入到epoll里面去,epoll设置监听事件EPOLLIN | EPOLLET; 主要是监听的是加入到epoll中的socket是否可读(因为我的需求是客户端连上了server就会马上向server发送一份数据的)。其它的部分在主线程中处理。

主线程:是一个无线循环,epoll_wait 函数相当于把客户端的连接从epoll中拿出来(因为我们监听的是EPOLLIN | EPOLLET)说明这个时候客户端有数据发送过来)。再通过recv_buffer_from_fd 函数把客户端发送过来的数据读出来。然后其他的一切就抛给线程池去处理。

线程池:(代码中我会在池里面创建15个线程) 双向链表。加入线程就是在链表后面加一个链表项,链表的前面会一个一个被拿出来处理。主要是malloc 函数free函数,sem_wait函数sem_post的处理(sem_wait 会阻塞当值大于0是会减一,sem_post是值加一)。typedef void (FUNC)(void arg, int index);是我们自定义的线程的逻辑处理部分,arg是参数,index是第几个线程处理(我们隐形的给每个线程都标了号),例如代码中的respons_stb_info,更加具体可以看看代码里面是怎么实现的。聪明的你也可以改掉这块的内容改成动态线程池,当某个时刻的处理比较多的时候能够动态的增加线程,而不像我代码里面的是固定的。

数据库连接池:按照我的需求在处理客户端请求数据的时候是要访问数据库的。就是一下子创建出一堆的数据连接。要访问数据库的时候先去数据库连接池中找出空闲的连接,具体可以看下代码。使用的时候可以参考下database_processc文件(代码中数据库连接池和线程池中的个数是一样的)。这里我想说下get_db_connect_from_pool这个函数,我用了随机数,我是为了不想每次都从0开始去判断哪个连接没有用到。为了数据库连接池中的每个链接都能等概率的使用到,具体的还是可以看下代码的实现。

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » 什么是线程池,如何使用,为什么要用

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情