Python之路【第八篇】python实现线程池
线程池概念
什么是线程池?
诸如web服务器、数据库服务器、文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务。
构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就创建一个新的服务对象,然后在新的服务对象中为请求服务。
但当有大量请求并发访问时,服务器不断的创建和销毁对象的开销很大。
所以提高服务器效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这样就引入了“池”的概念,
“池”的概念使得人们可以定制一定量的资源,然后对这些资源进行复用,而不是频繁的创建和销毁。
线程池是预先创建线程的一种技术。
这些线程都是处于睡眠状态,即均为启动,不消耗CPU,而只是占用较小的内存空间。
当请求到来之后,缓冲池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。
当预先创建的线程都处于运行状态,即预制线程不够,线程池可以自由创建一定数量的新线程,用于处理更多的请求。
当系统比较闲的时候,也可以通过移除一部分一直处于停用状态的线程。
线程池的注意事项
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。在使用线程池时需注意线程池大小与性能的关系,注意并发风险、死锁、资源不足和线程泄漏等问题。
1、线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。
一般来说,如果代码结构合理的话,线程数目与CPU 数量相适合即可。
如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。
2、并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
3、线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。
线程池要点
线程池要点:
线程池要点:
1、通过判断等待的任务数量和线程池中的最大值,取最小值来判断开启多少线程来工作
比如:
任务数是3,进程池最大20 ,那么咱们只需要开启3个线程就行了。
任务数是500,进程池是20,那么咱们只开20个线程就可以了。
取最小值 2、实现线程池正在运行,有一个查看的功能,查看一下现在线程里面活跃的线程是多少等待的是多少? 线程总共是多少,等待中多少,正在运行中多少
作用:
方便查看当前线程池状态
能获取到这个之后就可以当线程一直处于空闲状态 查看状态用:上下文管理来做,非常nice的一点 3、关闭线程
简单线程池实现
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t'
import Queue
import threading
import time '''
这个简单的例子的想法是通过:
1、利用Queue特性,在Queue里创建多个线程对象
2、那我执行代码的时候,去queue里去拿线程!
如果线程池里有可用的,直接拿。
如果线程池里没有可用,那就等。
3、线程执行完毕,归还给线程池
''' class ThreadPool(object): #创建线程池类
def __init__(self,max_thread=20):#构造方法,设置最大的线程数为20
self.queue = Queue.Queue(max_thread) #创建一个队列
for i in xrange(max_thread):#循环把线程对象加入到队列中
self.queue.put(threading.Thread)
#把线程的类名放进去,执行完这个Queue def get_thread(self):#定义方法从队列里获取线程
return self.queue.get() def add_thread(self):#定义方法在队列里添加线程
self.queue.put(threading.Thread) pool = ThreadPool(10) def func(arg,p):
print arg
time.sleep(2)
p.add_thread() #当前线程执行完了,我在队列里加一个线程! for i in xrange(300):
thread = pool.get_thread() #线程池10个线程,每一次循环拿走一个!默认queue.get(),如果队列里没有数据就会等待。
t = thread(target=func,args=(i,pool))
t.start() '''
self.queue.put(threading.Thread) 添加的是类不是对象,在内存中如果相同的类只占一份内存空间
并且如果这里存储的是对象的话每次都的新增都得在内存中开辟一段内存空间 还有如果是对象的话:下面的这个语句就不能这么调用了!
for i in xrange(300):
thread = pool.get_thread()
t = thread(target=func,args=(i,pool))
t.start()
通过查看源码可以知道,在thread的构造函数中:self.__args = args self.__target = target 都是私有字段那么调用就应该这么写 for i in xrange(300):
ret = pool.get_thread()
ret._Thread__target = func
ret._Thread__args = (i,pool)
ret.start()
'''
simple_pool.py
复杂线程池需要知道的知识点
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t' import Queue
obj = object() #object也是一个类,我创建了一个对象obj q = Queue.Queue()
for i in range(10):
print id(obj)#看萝卜号
q.put(obj)
'''
这个队列里有10个萝卜(萝卜=obj),但是这10个萝卜只是个投影。
我们在for循环的时候put到队列里,obj有变化吗?是否有新开辟空间?显然没有
'''
knowledge_point_1.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t'
import contextlib
import threading
import time
import random doing = []
def number(l2):
while True:
print len(l2)
time.sleep(1) t = threading.Thread(target=number,args=(doing,)) #开启一个线程,每一秒打印列表,当前工作中的线程数量
t.start() #添加管理上下文的装饰器
@contextlib.contextmanager
def show(li,iterm):
li.append(iterm)
yield
'''
yield冻结这次操作,就出去了,with就会捕捉到,然后就会执行with下的代码块,当with下的代码块
执行完毕后就会回来继续执行yield下面没有执行的代码块!
然后就执行完毕了
如果with代码块中的非常耗时,那么doing的长度是不是一直是1,说明他没执行完呢?我们就可以获取到正在执行的数量,当他with执行完毕后
执行yield的后续的代码块。把他移除后就为0了!
'''
li.remove(iterm) def task(arg):
with show(doing,1):#通过with管理上下文进行切换
print len(doing)
time.sleep(10) #等待10秒这里可以使用random模块来操作~ for i in range(20): #开启20个线程执行
temp = threading.Thread(target=task,args=(i,))
temp.start() '''
作用:我们要记录正在工作的的列表
比如正在工作的线程我把加入到doing这个列表中,如果工作完成的把它从doing列表中移除。
通过这个机制,就可以获取现在正在执行的线程都有多少
'''
knowledge_point_2.py
线程池实现
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t'
from Queue import Queue
import contextlib
import threading WorkerStop = object() class ThreadPool:
workers = 0
threadFactory = threading.Thread
currentThread = staticmethod(threading.currentThread) def __init__(self, maxthreads=20, name=None):
self.q = Queue(0) #这里创建一个队列,如果是0的话表示不限制,现在这个队列里放的是任务
self.max = maxthreads #定义最大线程数
self.name = name
self.waiters = []#这两个是用来计数的
self.working = []#这两个是用来技术的 def start(self):
#self.max 最大线程数
#q.qisze(),任务个数
needSize = self.q.qsize()
while self.workers < min(self.max, needSize):#min(10,20)取最小值
#wokers默认为0 【workers = 0】
'''
举例来说:
while self.workers < min(self.max, needSize):
这个循环,比如最大线程为20,咱们的任务个数为10,取最小值为10
每次循环开1个线程,并且workers自增1,那么循环10次后,开了10个线程了workers = 10 ,那么workers就不小于10了
就不开线程了,我线程开到最大了,你们这10个线程去消耗这10个任务去吧
并且这里不阻塞,创建完线程就去执行了!
每一个线程都去执行_worker方法去了
'''
self.startAWorker() def startAWorker(self):
self.workers += 1
newThread = self.threadFactory(target=self._worker, name='shuaige') #创建一个线程并去执行_worker方法
newThread.start() def callInThread(self, func, *args, **kw):
self.callInThreadWithCallback(None, func, *args, **kw) def callInThreadWithCallback(self, onResult, func, *args, **kw):
o = (func, args, kw, onResult)
self.q.put(o) @contextlib.contextmanager
def _workerState(self, stateList, workerThread):
stateList.append(workerThread)
try:
yield
finally:
stateList.remove(workerThread) def _worker(self):
ct = self.currentThread()
o = self.q.get() #去队列里取任务,如果有任务就O就会有值,每个任务是个元组,有方法,有参数
while o is not WorkerStop:
with self._workerState(self.working, ct): #上下文切换
function, args, kwargs, onResult = o
del o
try:
result = function(*args, **kwargs)
success = True
except:
success = False
if onResult is None:
pass
else:
pass del function, args, kwargs if onResult is not None:
try:
onResult(success, result)
except:
#context.call(ctx, log.err)
pass del onResult, result with self._workerState(self.waiters, ct): #当线程工作完闲暇的时候,在去取任务执行
o = self.q.get() def stop(self): #定义关闭线程方法
while self.workers: #循环workers值
self.q.put(WorkerStop) #在队列中增加一个信号~
self.workers -= 1 #workers值-1 直到所有线程关闭 def show(arg):
import time
time.sleep(1)
print arg pool = ThreadPool(10) #创建500个任务,队列里添加了500个任务
#每个任务都是一个元组(方法名,动态参数,动态参数,默认为NoNe)
for i in range(100):
pool.callInThread(show, i) pool.start() #队列添加完成之后,开启线程让线程一个一个去队列里去拿 pool.stop() #当上面的任务都执行完之后,线程中都在等待着在队列里去数据呢!
'''
我们要关闭所有的线程,执行stop方法,首先workers这个值是当前的线程数量,我们给线程发送一个信号“WorkerStop”
在线程的工作里: while o is not WorkerStop: 如果线程获取到这个值就不执行了,然后这个线程while循环就停止了,等待
python的垃圾回收机制,回收。 然后在self.workers -= 1 ,那么所有的线程收到这个信号之后就会停止!!!
over~
'''
更多请参考:http://www.cnblogs.com/wupeiqi/articles/4839959.html
Python之路【第八篇】python实现线程池的更多相关文章
- Python之路(第八篇)Python内置函数、zip()、max()、min()
一.python内置函数 abs() 求绝对值 例子 print(abs(-2)) all() 把序列中每一个元素做布尔运算,如果全部都是true,就返回true, 但是如果是空字符串.空列表也返回t ...
- 【Python之路】第九篇--Python基础之线程、进程和协程
进程与线程之间的关系 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除.线程可与属于同一进程的其它线程共享进程所拥有的全 ...
- 【Python之路】特别篇--Python面向对象(进阶篇)
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- Python之路(第七篇)Python作用域、匿名函数、函数式编程、map函数、filter函数、reduce函数
一.作用域 return 可以返回任意值例子 def test1(): print("test1") def test(): print("test") ret ...
- Python之路(第五篇) Python基本数据类型集合、格式化、函数
一.变量总结 1.1 变量定义 记录某种状态或者数值,并用某个名称代表这个数值或状态. 1.2 变量在内存中的表现形式 Python 中一切皆为对象,数字是对象,列表是对象,函数也是对象,任何东西都是 ...
- 【Python之路】特别篇--Python切片
字符串切片操作 切片操作符是序列名后跟一个方括号,方括号中有一对可选的数字,并用冒号分割. 注意: 数是可选的,而冒号是必须的. consequence[start:end:step] 切片操作符中的 ...
- 【Python之路】特别篇--Python内置函数
abs() 求绝对值 i = abs(-100) print(i) # 100 all() 循环里面的参数 如果每个元素都为真,那么all返回值为真 假: 0 False None "&q ...
- 【Python之路】特别篇--Python文件操作
文件操作 open函数,该函数用于文件处理 操作文件时,一般需要经历如下步骤: (1)打开文件 (2)操作文件 一.打开文件 文件句柄 = open('文件路径', '模式','编码') 打开文件时, ...
- 【Python之路】特别篇--Python装饰器
前情提要 1. 作用域 在python中,函数会创建一个新的作用域.python开发者可能会说函数有自己的命名空间,差不多一个意思.这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去 ...
- 【Python之路】特别篇--Python正则表达式
正则表达式的基础 正则表达式并不是Python的一部分. 正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大. 得益于这一点 ...
随机推荐
- 【CodeForces 698A】Vacations
f[i][0..2]表示第i天休息|运动|比赛最少的休息天数. #include <cstdio> #include <cstring> #include <algori ...
- Java反序列化测试
前言:有没有想过,如何将对象进行“加密”后写入磁盘?序列化帮你实现! 1.概念 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写 ...
- 基础R绘图
前言: 在前面介绍了R的基础入门语法之后,现也将最近整理好的一些R的基础绘图实例提供给需要的朋友参考.(温馨提示:代码慎用!按照本博文实例进行练习的话最好能做到举一反三.代码多敲方为上策,切不可隔岸观 ...
- bzoj4204: 取球游戏
好神啊.. 首先递推随便yy一下就行了 然后发现可以用矩阵优化,不过显然是n^3logk的,不资磁 于是就有了性质,这个转移矩阵显然是一个循环矩阵(并不知道) 循环矩阵乘循环矩阵还是循环矩阵 然后就可 ...
- leetcode 402. Remove K Digits
Given a non-negative integer num represented as a string, remove k digits from the number so that th ...
- 【BZOJ-4631】踩气球 线段树 + STL
4631: 踩气球 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 224 Solved: 114[Submit][Status][Discuss] ...
- Cache Helper类
using System; using System.Collections.Generic; using System.Web; using System.Collections; using Sy ...
- Android成长日记-仿跑马灯的TextView
在程序设计中有时候一行需要显示多个文字,这时候在Android中默认为分为两行显示,但是对于必须用一行显示的文字需要如何使用呢? ----------------------------------- ...
- SELINUX、Security Access Control Strategy && Method And Technology Research - 安全访问控制策略及其方法技术研究
catalog . 引言 . 访问控制策略 . 访问控制方法.实现技术 . SELINUX 0. 引言 访问控制是网络安全防范和客户端安全防御的主要策略,它的主要任务是保证资源不被非法使用.保证网络/ ...
- 如何保持自己 fork 的项目和原始项目同步
首先先通过 github 的 web 页面 fork 目标的项目 前提是自己已经设置好了git,并且配置了相应的权限 然后使用git clone命令在本地克隆自己 fork 的项目: git clon ...