先来看个栗子:
下面来看一下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_page13 14 # 提取网页中图片的URL15 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 imglist20 21 class getImg(threading.Thread):22 def __init__(self, queue, thread_name=0): # 线程公用一个队列23 threading.Thread.__init__(self)24 self.queue = queue25 self.thread_name = thread_name26 self.start() # 启动线程27 28 # 使用队列实现进程间通信29 def run(self):30 global count31 while (True):32 imgurl = self.queue.get() # 调用队列对象的get()方法从队头删除并返回一个项目33 urllib.urlretrieve(imgurl, 'E:\mnt\girls\%s.jpg' % count)34 count += 135 if self.queue.empty():36 break37 self.queue.task_done() # 当使用者线程调用 task_done() 以表示检索了该项目、并完成了所有的工作时,那么未完成的任务的总数就会减少。38 imglist = []39 def main():40 global imglist41 url = "http://huaban.com/favorite/beauty/" # 要爬的网页地址42 html = getHtml(url)43 imglist = getUrl(html)44 45 def main_1():46 global count47 threads = []48 count = 049 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 = 10000000012 thread1 = threading.Thread(target=countdown, args=(COUNT,))13 thread1.start()14 thread1.join()15 16 def task2():17 COUNT = 10000000018 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),导致效率更低。
作者: 出处: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。