python 多线程剖析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 多线程剖析相关的知识,希望对你有一定的参考价值。
先来看个栗子:
下面来看一下I/O秘籍型的线程,举个栗子——爬虫,下面是爬下来的图片用4个线程去写文件
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import re 5 import urllib 6 import threading 7 import Queue 8 import timeit 9 10 def getHtml(url): 11 html_page = urllib.urlopen(url).read() 12 return html_page 13 14 # 提取网页中图片的URL 15 def getUrl(html): 16 pattern = r‘src="(http://img.*?)"‘ # 正则表达式 17 imgre = re.compile(pattern) 18 imglist = re.findall(imgre, html) # re.findall(pattern,string) 在string中寻找所有匹配成功的字符串,以列表形式返回值 19 return imglist 20 21 class getImg(threading.Thread): 22 def __init__(self, queue, thread_name=0): # 线程公用一个队列 23 threading.Thread.__init__(self) 24 self.queue = queue 25 self.thread_name = thread_name 26 self.start() # 启动线程 27 28 # 使用队列实现进程间通信 29 def run(self): 30 global count 31 while (True): 32 imgurl = self.queue.get() # 调用队列对象的get()方法从队头删除并返回一个项目 33 urllib.urlretrieve(imgurl, ‘E:\\mnt\\girls\\%s.jpg‘ % count) 34 count += 1 35 if self.queue.empty(): 36 break 37 self.queue.task_done() # 当使用者线程调用 task_done() 以表示检索了该项目、并完成了所有的工作时,那么未完成的任务的总数就会减少。 38 imglist = [] 39 def main(): 40 global imglist 41 url = "http://huaban.com/favorite/beauty/" # 要爬的网页地址 42 html = getHtml(url) 43 imglist = getUrl(html) 44 45 def main_1(): 46 global count 47 threads = [] 48 count = 0 49 queue = Queue.Queue() 50 # 将所有任务加入队列 51 for img in imglist: 52 queue.put(img) 53 # 多线程爬去图片 54 for i in range(4): 55 thread = getImg(queue, i) 56 threads.append(thread) 57 # 阻塞线程,直到线程执行完成 58 for thread in threads: 59 thread.join() 60 61 if __name__ == ‘__main__‘: 62 main() 63 t = timeit.Timer(main_1) 64 print t.timeit(1)
4个线程的执行耗时为:0.421320716723秒
修改一下main_1换成单线程的:
1 def main_1(): 2 global count 3 threads = [] 4 count = 0 5 queue = Queue.Queue() 6 # 将所有任务加入队列 7 for img in imglist: 8 queue.put(img) 9 # 多线程爬去图片 10 for i in range(1): 11 thread = getImg(queue, i) 12 threads.append(thread) 13 # 阻塞线程,直到线程执行完成 14 for thread in threads: 15 thread.join()
单线程的执行耗时为:1.35626623274秒
再来看一个:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import threading 4 import timeit 5 6 def countdown(n): 7 while n > 0: 8 n -= 1 9 10 def task1(): 11 COUNT = 100000000 12 thread1 = threading.Thread(target=countdown, args=(COUNT,)) 13 thread1.start() 14 thread1.join() 15 16 def task2(): 17 COUNT = 100000000 18 thread1 = threading.Thread(target=countdown, args=(COUNT // 2,)) 19 thread2 = threading.Thread(target=countdown, args=(COUNT // 2,)) 20 thread1.start() 21 thread2.start() 22 thread1.join() 23 thread2.join() 24 25 if __name__ == ‘__main__‘: 26 t1 = timeit.Timer(task1) 27 print "countdown in one thread ", t1.timeit(1) 28 t2 = timeit.Timer(task2) 29 print "countdown in two thread ", t2.timeit(1)
task1是单线程,task2是双线程,在我的4核的机器上的执行结果:
countdown in one thread 3.59939150155
countdown in two thread 9.87704289712
天呐,双线程比单线程计算慢了2倍多,这是为什么呢,因为countdown是CPU密集型任务(计算嘛)
I/O密集型任务:线程做I/O处理的时候会释放GIL,其他线程获得GIL,当该线程再做I/O操作时,又会释放GIL,如此往复;
CPU密集型任务:在多核多线程比单核多线程更差,原因是单核多线程,每次释放GIL,唤醒的哪个线程都能获取到GIL锁,所以能够无缝执行(单核多线程的本质就是顺序执行),但多核,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0(CPU0上可能不止一个线程)拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。
以上是关于python 多线程剖析的主要内容,如果未能解决你的问题,请参考以下文章