Python实现线程池
一、线程池概念
什么是线程池?
诸如web服务器、数据库服务器、文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务。
构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就创建一个新的服务对象,然后在新的服务对象中为请求服务。
但当有大量请求并发访问时,服务器不断的创建和销毁对象的开销很大。
所以提高服务器效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这样就引入了“池”的概念,
“池”的概念使得人们可以定制一定量的资源,然后对这些资源进行复用,而不是频繁的创建和销毁。
线程池是预先创建线程的一种技术。
这些线程都是处于睡眠状态,即均为启动,不消耗CPU,而只是占用较小的内存空间。
当请求到来之后,缓冲池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。
当预先创建的线程都处于运行状态,即预制线程不够,线程池可以自由创建一定数量的新线程,用于处理更多的请求。
当系统比较闲的时候,也可以通过移除一部分一直处于停用状态的线程。
线程池的注意事项
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。在使用线程池时需注意线程池大小与性能的关系,注意并发风险、死锁、资源不足和线程泄漏等问题。
1、线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。
一般来说,如果代码结构合理的话,线程数目与CPU 数量相适合即可。
如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。
2、并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
3、线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。
二、线程池要点
线程池要点:
线程池要点: 1、通过判断等待的任务数量和线程池中的最大值,取最小值来判断开启多少线程来工作 比如: 任务数是3,进程池最大20 ,那么咱们只需要开启3个线程就行了。 任务数是500,进程池是20,那么咱们只开20个线程就可以了。 取最小值 2、实现线程池正在运行,有一个查看的功能,查看一下现在线程里面活跃的线程是多少等待的是多少? 线程总共是多少,等待中多少,正在运行中多少 作用: 方便查看当前线程池状态 能获取到这个之后就可以当线程一直处于空闲状态 查看状态用:上下文管理来做,非常nice的一点 3、关闭线程
简单线程池实现
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 __author__ = 'luo_t' 4 import Queue 5 import threading 6 import time 7 8 ''' 9 这个简单的例子的想法是通过: 10 1、利用Queue特性,在Queue里创建多个线程对象 11 2、那我执行代码的时候,去queue里去拿线程! 12 如果线程池里有可用的,直接拿。 13 如果线程池里没有可用,那就等。 14 3、线程执行完毕,归还给线程池 15 ''' 16 17 class ThreadPool(object): #创建线程池类 18 def __init__(self,max_thread=20):#构造方法,设置最大的线程数为20 19 self.queue = Queue.Queue(max_thread) #创建一个队列 20 for i in xrange(max_thread):#循环把线程对象加入到队列中 21 self.queue.put(threading.Thread) 22 #把线程的类名放进去,执行完这个Queue 23 24 def get_thread(self):#定义方法从队列里获取线程 25 return self.queue.get() 26 27 def add_thread(self):#定义方法在队列里添加线程 28 self.queue.put(threading.Thread) 29 30 pool = ThreadPool(10) 31 32 def func(arg,p): 33 print arg 34 time.sleep(2) 35 p.add_thread() #当前线程执行完了,我在队列里加一个线程! 36 37 for i in xrange(300): 38 thread = pool.get_thread() #线程池10个线程,每一次循环拿走一个!默认queue.get(),如果队列里没有数据就会等待。 39 t = thread(target=func,args=(i,pool)) 40 t.start() 41 42 43 ''' 44 self.queue.put(threading.Thread) 添加的是类不是对象,在内存中如果相同的类只占一份内存空间 45 并且如果这里存储的是对象的话每次都的新增都得在内存中开辟一段内存空间 46 47 还有如果是对象的话:下面的这个语句就不能这么调用了! 48 for i in xrange(300): 49 thread = pool.get_thread() 50 t = thread(target=func,args=(i,pool)) 51 t.start() 52 通过查看源码可以知道,在thread的构造函数中:self.__args = args self.__target = target 都是私有字段那么调用就应该这么写 53 54 for i in xrange(300): 55 ret = pool.get_thread() 56 ret._Thread__target = func 57 ret._Thread__args = (i,pool) 58 ret.start() 59 ''' 60 61 simple_pool.py
复杂线程池需要知道的知识
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 __author__ = 'luo_t' 4 5 import Queue 6 obj = object() #object也是一个类,我创建了一个对象obj 7 8 q = Queue.Queue() 9 for i in range(10): 10 print id(obj)#看萝卜号 11 q.put(obj) 12 ''' 13 这个队列里有10个萝卜(萝卜=obj),但是这10个萝卜只是个投影。 14 我们在for循环的时候put到队列里,obj有变化吗?是否有新开辟空间?显然没有 15 ''' 16 17 knowledge_point_1.py
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 __author__ = 'luo_t' 4 import contextlib 5 import threading 6 import time 7 import random 8 9 doing = [] 10 def number(l2): 11 while True: 12 print len(l2) 13 time.sleep(1) 14 15 t = threading.Thread(target=number,args=(doing,)) #开启一个线程,每一秒打印列表,当前工作中的线程数量 16 t.start() 17 18 19 #添加管理上下文的装饰器 20 @contextlib.contextmanager 21 def show(li,iterm): 22 li.append(iterm) 23 yield 24 ''' 25 yield冻结这次操作,就出去了,with就会捕捉到,然后就会执行with下的代码块,当with下的代码块 26 执行完毕后就会回来继续执行yield下面没有执行的代码块! 27 然后就执行完毕了 28 如果with代码块中的非常耗时,那么doing的长度是不是一直是1,说明他没执行完呢?我们就可以获取到正在执行的数量,当他with执行完毕后 29 执行yield的后续的代码块。把他移除后就为0了! 30 ''' 31 li.remove(iterm) 32 33 34 def task(arg): 35 with show(doing,1):#通过with管理上下文进行切换 36 print len(doing) 37 time.sleep(10) #等待10秒这里可以使用random模块来操作~ 38 39 for i in range(20): #开启20个线程执行 40 temp = threading.Thread(target=task,args=(i,)) 41 temp.start() 42 43 ''' 44 作用:我们要记录正在工作的的列表 45 比如正在工作的线程我把加入到doing这个列表中,如果工作完成的把它从doing列表中移除。 46 通过这个机制,就可以获取现在正在执行的线程都有多少 47 ''' 48 49 knowledge_point_2.py
三、线程池实现
1 import queue 2 import threading 3 import time 4 5 class ThreadPool: 6 def __init__(self,maxsize=5): #定义构造方法 7 self.maxsize = maxsize #线程池大小为5 8 self._q = queue.Queue(maxsize) 9 for i in range(maxsize): 10 self._q.put(threading.Thread) #先往队列里插入的线程池大小的元素,元素为Threading.Thread类,等待处理请求 11 def get_thread(self): #定义get_thread方法来去处理请求 12 return self._q.get() 13 14 def add_thread(self): #定义add_thread方法来请求线程池 15 self._q.put(threading.Thread) 16 17 pool = ThreadPool(5) #创建线程池 18 19 def task(arg,p): 20 """ 21 在队列里添加一个元素,线程执行完会自动关闭,需要在重新添加新的线程对象 22 :param arg: 循环的数值 23 :param p: 线程池的对象 24 :return: 25 """ 26 print(arg) 27 time.sleep(1) 28 p.add_thread() 29 30 for i in range(100): 31 t = pool.get_thread() #get,threading.Thread类 32 obj = t(target=task,args=(i,pool,)) 33 obj.start()
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 5 import queue 6 import contextlib 7 import threading 8 9 WorkerStop = object() #定义一个线程停止的标志,如果在队列里取到这个值,线程停止 10 11 12 class ThreadPool: 13 workers = 0 #默认workers定义为0 14 threadFactory = threading.Thread 15 currentThread = staticmethod(threading.currentThread) #创建静态方法为当前线程活跃的线程个数 16 17 def __init__(self, maxthreads=20, name=None): 18 self.q = queue.Queue(0) #这里创建一个队列,如果是0的话表示不限制,现在这个队列里放的是任务 19 self.max = maxthreads #定义最大线程数 20 self.name = name 21 self.waiters = [] #这两个是用来计数的 22 self.working = [] #用来记录正在工作的线程数 23 24 def start(self): 25 ''' 26 举例来说: 27 while self.workers < min(self.max, needSize): 28 这个循环,比如最大线程为20,咱们的任务个数为10,取最小值为10 29 每次循环开1个线程,并且workers自增1,那么循环10次后,开了10个线程了workers = 10 ,那么workers就不小于10了 30 就不开线程了,我线程开到最大了,你们这10个线程去消耗这10个任务去吧 31 并且这里不阻塞,创建完线程就去执行了! 32 每一个线程都去执行_worker方法去了 33 ''' 34 needSize = self.q.qsize() #获取当前队列的任务长度 35 while self.workers < min(self.max, needSize): #wokers默认为0 【workers = 0】 36 self.startAWorker() 37 38 def startAWorker(self): 39 self.workers += 1 #每个线程过来自增1 40 newThread = self.threadFactory(target=self._worker, name='shuaige') #创建一个线程并去执行_worker方法 41 newThread.start() 42 43 def callInThread(self, func, *args, **kw): 44 self.callInThreadWithCallback(None, func, *args, **kw) 45 46 def callInThreadWithCallback(self, onResult, func, *args, **kw): #提交任务到队列中 47 o = (func, args, kw, onResult) 48 self.q.put(o) 49 50 51 @contextlib.contextmanager #提供一种针对函数级别的上下文管理机制 52 def _workerState(self, stateList, workerThread): #记录线程数量的状态,加到stateList列表里, 53 stateList.append(workerThread) 54 try: 55 yield 56 finally: 57 stateList.remove(workerThread) 58 59 def _worker(self): 60 ct = self.currentThread() #当前活跃的线程个数 61 o = self.q.get() #去队列里取任务,如果有任务就O就会有值,每个任务是个元组,有方法,有参数 62 while o is not WorkerStop: #如果取出来的值不是WorkerStop就循环 63 with self._workerState(self.working, ct): #上下文切换 64 function, args, kwargs, onResult = o 65 del o 66 try: 67 result = function(*args, **kwargs) 68 success = True 69 except: 70 success = False 71 if onResult is None: 72 pass 73 else: 74 pass 75 76 del function, args, kwargs 77 78 if onResult is not None: 79 try: 80 onResult(success, result) 81 except: 82 #context.call(ctx, log.err) 83 pass 84 85 del onResult, result 86 87 with self._workerState(self.waiters, ct): #当线程工作完闲暇的时候,在去取任务执行 88 o = self.q.get() 89 90 def stop(self): #定义关闭线程方法 91 while self.workers: #循环workers值 92 self.q.put(WorkerStop) #在队列中发送一个停止信号 93 self.workers -= 1 #workers值-1 直到所有线程关闭 94 95 96 def show(arg): #每个任务处理间隔1秒 97 import time 98 time.sleep(1) 99 print(arg) 100 101 102 pool = ThreadPool(10) 103 104 #创建100个任务,队列里添加了100个任务 105 #每个任务都是一个元组(方法名,动态参数,动态参数,默认为NoNe) 106 for i in range(100): 107 pool.callInThread(show, i) 108 109 pool.start() #队列添加完成之后,开启线程让线程一个一个去队列里去拿 110 111 pool.stop() #当上面的任务都执行完之后,线程中都在等待着在队列里去数据呢! 112 ''' 113 我们要关闭所有的线程,执行stop方法,首先workers这个值是当前的线程数量,我们给线程发送一个信号“WorkerStop” 114 在线程的工作里: while o is not WorkerStop: 如果线程获取到这个值就不执行了,然后这个线程while循环就停止了,等待 115 python的垃圾回收机制,回收。 116 117 然后在self.workers -= 1 ,那么所有的线程收到这个信号之后就会停止!!! 118 over~ 119 '''


浙公网安备 33010602011771号