在给大家分享之前,这里推荐下我自己建的python群:595266089,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括2017最新的python企业案例学习资料和零基础入门教程,欢迎初学和进阶中的小伙伴。 
 
当初在刚学习python多线程时,上网搜索资料几乎都是一片倒的反应python没有真正意义上的多线程,python多线程就是鸡肋。当时不明所以,只是了解到python带有GIL解释器锁的概念,同一时刻只能有一个线程在运行,遇到IO操作才会释放切换。那么,python多线程是否真的很鸡肋呢?要解决这个疑惑,我想必须亲自动手测试。 
经过对比python与java的多线程测试,我发现python多线程的效率确实不如java,但远还没有达到鸡肋的程度,那么跟其他机制相比较呢? 
观点:用多进程替代多线程需求 
辗转了多篇博文,我看到了一些网友的观点,觉得应该使用python多进程来代替多线程的需求,因为多进程不受GIL的限制。于是我便动手使用多进程去解决一些并发问题,期间也遇到了一些坑,所幸大部分查找资料解决了,然后对多进程做了简单汇总介绍Python多进程。 
进群:960410445 即可获取数十套PDF! 
那么是否多进程能完全替代多线程呢?别急,我们继续往下看。 
观点:协程为最佳方案 
协程的概念目前来说是比较火热的,协程不同于线程的地方在于协程不是操作系统进行切换,而是由程序员编码进行切换的,也就是说切换是由程序员控制的,这样就没有了线程所谓的安全问题。协程的概念非常广而深,本文暂不做具体介绍,以后会单独成文。 
测试数据 
好了,网上的观点无非是使用多进程或者协程来代替多线程(当然换编程语言,换解释器之类方法除外),那么我们就来测试下这三者的性能之差。既然要公平测试,就应该考虑IO密集型与CPU密集型的问题,所以分两组数据进行测试。 
IO密集型测试 
测试IO密集型,我选择最常用的爬虫功能,计算爬虫访问bing所需要的时间。(主要测试多线程与协程,单线程与多进程就不测了,因为没有必要) 
测试代码: 
#! -*- coding:utf-8 -*- 
fromgevent importmonkey;monkey.patch_all() 
importgevent 
importtime 
importthreading 
importurllib2 
defurllib2_(url): 
try: 
urllib2.urlopen(url,timeout=10).read() 
exceptException,e: 
printe 
defgevent_(urls): 
jobs=[gevent.spawn(urllib2_,url)forurl inurls] 
gevent.joinall(jobs,timeout=10) 
foriinjobs: 
i.join() 
defthread_(urls): 
a=[] 
forurl inurls: 
t=threading.Thread(target=urllib2_,args=(url,)) 
a.append(t) 
foriina: 
i.start() 
foriina: 
i.join() 
if__name__=="__main__": 
urls=["https://www.bing.com/"]*10 
t1=time.time() 
gevent_(urls) 
t2=time.time() 
print'gevent-time:%s' % str(t2-t1) 
thread_(urls) 
t4=time.time() 
print'thread-time:%s' % str(t4-t2) 
 
测试结果: 
访问10次 
gevent-time:0.380326032639 
thread-time:0.376606941223 
 
访问50次 
gevent-time:1.3358900547 
thread-time:1.59564089775 
 
访问100次 
gevent-time:2.42984986305 
thread-time:2.5669670105 
 
访问300次 
gevent-time:6.66330099106 
thread-time:10.7605059147 
 
从结果可以看出,当并发数不断增大时,协程的效率确实比多线程要高,但在并发数不是那么高时,两者差异不大。 
CPU密集型 
CPU密集型,我选择科学计算的一些功能,计算所需时间。(主要测试单线程、多线程、协程、多进程) 
测试代码: 
#! -*- coding:utf-8 -*- 
frommultiprocessingimportProcess aspro 
frommultiprocessing.dummyimportProcess asthr 
fromgevent importmonkey;monkey.patch_all() 
importgevent 
defrun(i): 
lists=range(i) 
list(set(lists)) 
if__name__=="__main__": 
''' 
多进程 
''' 
foriinrange(30): ##10-2.1s 20-3.8s 30-5.9s 
t=pro(target=run,args=(5000000,)) 
t.start() 
''' 
多线程 
''' 
# for i in range(30): ##10-3.8s 20-7.6s 30-11.4s 
# t=thr(target=run,)) 
# t.start() 
''' 
协程 
''' 
# jobs=[gevent.spawn(run,5000000) for i in range(30)] ##10-4.0s 20-7.7s 30-11.5s 
# gevent.joinall(jobs) 
# for i in jobs: 
# i.join() 
''' 
单线程 
''' 
# for i in range(30): ##10-3.5s 20-7.6s 30-11.3s 
# run(5000000) 
 
测试结果: 
- 
    
并发10次:【多进程】2.1s 【多线程】3.8s 【协程】4.0s 【单线程】3.5s 
     
    - 
    
并发20次:【多进程】3.8s 【多线程】7.6s 【协程】7.7s 【单线程】7.6s 
     
    - 
    
并发30次:【多进程】5.9s 【多线程】11.4s 【协程】11.5s 【单线程】11.3s 
     
 可以看到,在CPU密集型的测试下,多进程效果明显比其他的好,多线程、协程与单线程效果差不多。这是因为只有多进程完全使用了CPU的计算能力。在代码运行时,我们也能够看到,只有多进程可以将CPU使用率占满。 
本文结论 
从两组数据我们不难发现,python多线程并没有那么鸡肋。如若不然,Python3为何不去除GIL呢?对于此问题,Python社区也有两派意见,这里不再论述,我们应该尊重Python之父的决定。 
至于何时该用多线程,何时用多进程,何时用协程?想必答案已经很明显了。 
当我们需要编写并发爬虫等IO密集型的程序时,应该选用多线程或者协程(亲测差距不是特别明显);当我们需要科学计算,设计CPU密集型程序,应该选用多进程。当然以上结论的前提是,不做分布式,只在一台服务器上测试。 
答案已经给出,本文是否就此收尾?既然已经论述Python多线程尚有用武之地,那么就来介绍介绍其用法吧。 
Multiprocessing.dummy模块 
Multiprocessing.dummy用法与多进程Multiprocessing用法类似,只是在import包的时候,加上.dummy。 
用法参考Multiprocessing用法 
threading模块 
这是python自带的threading多线程模块,其创建多线程主要有2种方式。一种为继承threading类,另一种使用threading.Thread函数,接下来将会分别介绍这两种用法。 
Usage【1】 
利用threading.Thread()函数创建线程。 
代码: 
defrun(i): 
printi 
foriinrange(10): 
t=threading.Thread(target=run,args=(i,)) 
t.start() 
 
说明:Thread()函数有2个参数,一个是target,内容为子线程要执行的函数名称;另一个是args,内容为需要传递的参数。创建完子线程,将会返回一个对象,调用对象的start方法,可以启动子线程。 
线程对象的方法: 
- 
    
Start() 开始线程的执行 
     
    - 
    
Run() 定义线程的功能的函数 
     
    - 
    
Join(timeout=None) 程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout秒 
     
    - 
    
getName() 返回线程的名字 
     
    - 
    
setName() 设置线程的名字 
     
    - 
    
isAlive() 布尔标志,表示这个线程是否还在运行 
     
    - 
    
isDaemon() 返回线程的daemon标志 
     
    - 
    
setDaemon(daemonic) 把线程的daemon标志设为daemonic(一定要在start()函数前调用) 
     
    - 
    
t.setDaemon(True) 把父线程设置为守护线程,当父进程结束时,子进程也结束。 
     
 threading类的方法: 
Usage【2】 
通过继承threading类,创建线程。 
代码: 
importthreading 
classtest(threading.Thread): 
def__init__(self): 
threading.Thread.__init__(self) 
defrun(self): 
try: 
print"code one" 
except: 
pass 
foriinrange(10): 
cur=test() 
cur.start() 
foriinrange(10): 
cur.join() 
 
说明:此方法继承了threading类,并且重构了run函数功能。 
获取线程返回值问题 
有时候,我们往往需要获取每个子线程的返回值。然而通过调用普通函数,获取return值的方式在多线程中并不适用。因此需要一种新的方式去获取子线程返回值。 
代码: 
importthreading 
classtest(threading.Thread): 
def__init__(self): 
threading.Thread.__init__(self) 
defrun(self): 
self.tag=1 
defget_result(self): 
ifself.tag==1: 
returnTrue 
else: 
returnFalse 
f=test() 
f.start() 
whilef.isAlive(): 
continue 
printf.get_result() 
 
说明:多线程获取返回值的首要问题,就是子线程什么时候结束?我们应该什么时候去获取返回值?可以使用isAlive()方法判断子线程是否存活。 
控制线程运行数目 
当需要执行的任务非常多时,我们往往需要控制线程的数量,threading类自带有控制线程数量的方法。 
代码: 
importthreading 
maxs=10##并发的线程数量 
threadLimiter=threading.BoundedSemaphore(maxs) 
classtest(threading.Thread): 
def__init__(self): 
threading.Thread.__init__(self) 
defrun(self): 
threadLimiter.acquire()#获取 
try: 
print"code one" 
except: 
pass 
finally: 
threadLimiter.release()#释放 
foriinrange(100): 
cur=test() 
cur.start() 
foriinrange(100): 
cur.join() 
 
说明:以上程序可以控制多线程并发数为10,超过这个数量会引发异常。 
除了自带的方法,我们还可以设计其他方案: 
threads=[] 
''' 
创建所有线程 
''' 
foriinrange(10): 
t=threading.Thread(target=run,)) 
threads.append(t) 
''' 
启动列表中的线程 
''' 
fortinthreads: 
t.start() 
whileTrue: 
#判断正在运行的线程数量,如果小于5则退出while循环, 
#进入for循环启动新的进程.否则就一直在while循环进入死循环 
if(len(threading.enumerate())<5): 
break 
 
以上两种方式皆可以,本人更喜欢用下面那种方式。 
线程池 
importthreadpool 
defThreadFun(arg1,arg2): 
pass 
defmain(): 
device_list=[object1,object2,object3......,objectn]#需要处理的设备个数 
task_pool=threadpool.ThreadPool(8)#8是线程池中线程的个数 
request_list=[]#存放任务列表 
#首先构造任务列表 
fordeviceindevice_list: 
request_list.append(threadpool.makeRequests(ThreadFun,[((device,),{})])) 
#将每个任务放到线程池中,等待线程池中线程各自读取任务,然后进行处理,使用了map函数,不了解的可以去了解一下。 
map(task_pool.putRequest,request_list) 
#等待所有任务处理完成,则返回,如果没有处理完,则一直阻塞 
task_pool.poll() 
if__name__=="__main__": 
main() 
 
多进程问题,可以赶赴Python多进程(http://python.jobbole.com/87760/)现场,其他关于多线程问题,可以下方留言讨论 
申明:本文谈不上原创,其中借鉴了网上很多大牛的文章,本人只是在此测试论述Python多线程相关问题,并简单介绍Python多线程的基本用法,为新手朋友解惑。                         (编辑:莱芜站长网) 
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! 
                     |