转自:http://www.cnblogs.com/slider/archive/2012/06/20/2556256.html

引言

  对于 Python 来说,并不缺少并发选项,其标准库中包括了对线程、进程和异步 I/O 的支持。在许多情况下,通过创建诸如异步、线程和子进程之类的高层模块,Python 简化了各种并发方法的使用。除了标准库之外,还有一些第三方的解决方案,例如 Twisted、Stackless 和进程模块。本文重点关注于使用 Python 的线程,并使用了一些实际的示例进行说明。虽然有许多很好的联机资源详细说明了线程 API,但本文尝试提供一些实际的示例,以说明一些常见的线程使用模式。

  全局解释器锁 (Global Interpretor Lock) 说明 Python 解释器并不是线程安全的。当前线程必须持有全局锁,以便对 Python 对象进行安全地访问。因为只有一个线程可以获得 Python 对象/C API,所以解释器每经过 100 个字节码的指令,就有规律地释放和重新获得锁。解释器对线程切换进行检查的频率可以通过 sys.setcheckinterval()函数来进行控制。此外,还将根据潜在的阻塞 I/O 操作,释放和重新获得锁。有关更详细的信息,请参见参考资料部分中的 Gil and Threading State 和 Threading the Global Interpreter Lock。需要说明的是,因为 GIL,CPU 受限的应用程序将无法从线程的使用中受益。使用 Python 时,建议使用进程,或者混合创建进程和线程。

  首先弄清进程和线程之间的区别,这一点是非常重要的。线程与进程的不同之处在于,它们共享状态、内存和资源。对于线程来说,这个简单的区别既是它的优势,又是它的缺点。一方面,线程是轻量级的,并且相互之间易于通信,但另一方面,它们也带来了包括死锁、争用条件和高复杂性在内的各种问题。幸运的是,由于 GIL 和队列模块,与采用其他的语言相比,采用 Python 语言在线程实现的复杂性上要低得多。

  使用 Python 线程

  要继续学习本文中的内容,我假定您已经安装了 Python 2.5 或者更高版本,因为本文中的许多示例都将使用 Python 语言的新特性,而这些特性仅出现于 Python2.5 之后。要开始使用 Python 语言的线程,我们将从简单的 "Hello World" 示例开始:

  1. #! /usr/bin/env python
  2. #coding=utf-8
  3. import threading
  4. import datetime
  5.  
  6. class ThreadClass(threading.Thread):
  7. def run(self):
  8. now = datetime.datetime.now()
  9. print "%s says Hello World at time: %s" % (self.getName(),now)
  10.  
  11. for i in range(2):
  12. t = ThreadClass()
  13. t.start()

结果:

  1. Thread-1 says Hello World at time: 2012-06-20 14:43:26.981173
  2. Thread-2 says Hello World at time: 2012-06-20 14:43:26.981375

  仔细观察输出结果,您可以看到从两个线程都输出了 Hello World 语句,并都带有日期戳。如果分析实际的代码,那么将发现其中包含两个导入语句;一个语句导入了日期时间模块,另一个语句导入线程模块。类 ThreadClass 继承自 threading.Thread,也正因为如此,您需要定义一个 run 方法,以此执行您在该线程中要运行的代码。在这个 run 方法中唯一要注意的是,self.getName()是一个用于确定该线程名称的方法。

最后三行代码实际地调用该类,并启动线程。如果注意的话,那么会发现实际启动线程的是 t.start()。在设计线程模块时考虑到了继承,并且线程模块实际上是建立在底层线程模块的基础之上的。对于大多数情况来说,从 threading.Thread 进行继承是一种最佳实践,因为它创建了用于线程编程的常规 API。

例子2:

  1. class MyThread(threading.Thread):
  2. 27 def __init__(self, urllist, urlset):
  3. 28 threading.Thread.__init__(self)
  4. 29 self.urllist = urllist
  5. 30 self.urlset = urlset
  6. 31
  7. 32 def run(self):
  8. 33 while True:
  9. 34 listlock.acquire()
  10. 35 if self.urllist:
  11. 36 url = self.urllist.pop(0)
  12. 37 listlock.release()
  13. 38 else:
  14. 39 listlock.release()
  15. 40 break
  16. 41
  17. 42 setlock.acquire()
  18. 43 if len(self.urlset) >= 50:
  19. 44 setlock.release()
  20. 45 break
  21. 46 else:
  22. 47 if url in self.urlset:
  23. 48 setlock.release()
  24. 49 continue
  25. 50 else:
  26. 51 self.urlset.add(url)
  27. 52 setlock.release()
  28. 53 content = getWebPage(url)
  29. 54 analysisPage(content, self.urllist, self.urlset)
  30. 55
  31. 56 listlock = threading.RLock()
  32. 57 setlock = threading.RLock()
  33. 58
  34. 59 if __name__ == '__main__':
  35. 60 starturl = 'http://www.cnblogs.com/\n'
  36. 61 content = getWebPage(starturl)
  37. 62 #urlset存放已访问过的网页url
  38. 63 #urllist存放待访问的网页url
  39. 64 urlset = set([starturl])
  40. 65 urllist = []
  41. 66 analysisPage(content, urllist, urlset)
  42. 67 tlist = []
  43. 68 for i in range(4):
  44. 69 t = MyThread(urllist, urlset)
  45. 70 t.start()
  46. 71 tlist.append(t)
  47. 72 for t in tlist:
  48. 73 t.join()
  49. 74 f = open('url.txt', 'w')
  50. 75 f.writelines(list(urlset))
  51. 76 f.close()

使用线程队列

  如前所述,当多个线程需要共享数据或者资源的时候,可能会使得线程的使用变得复杂。线程模块提供了许多同步原语,包括信号量、条件变量、事件和锁。当这些选项存在时,最佳实践是转而关注于使用队列。相比较而言,队列更容易处理,并且可以使得线程编程更加安全,因为它们能够有效地传送单个线程对资源的所有访问,并支持更加清晰的、可读性更强的设计模式,如”URL 获取线程化

  1. #! /usr/bin/env python
  2. #coding=utf-8
  3. import urllib2
  4. import time
  5. import Queue
  6. import threading
  7.  
  8. hosts = ["http://yahoo.com", "http://baidu.com", "http://amazon.com","http://ibm.com", "http://apple.com"]
  9.  
  10. queue = Queue.Queue()
  11.  
  12. class ThreadUrl(threading.Thread):
  13. '''Theaded url grab'''
  14. def __init__(self,queue):
  15. threading.Thread.__init__(self)
  16. self.queue = queue
  17.  
  18. def run(self):
  19. """docstring for run"""
  20. while True:
  21. # grabs host from Queue
  22. host = self.queue.get()
  23.  
  24. #grabs urls of hosts and prints first 1024 bytes of page
  25. url = urllib2.urlopen(host)
  26. print url.read(1024)
  27.  
  28. #signals to queue job is done
  29. self.queue.task_done()
  30.  
  31. start = time.time()
  32. def main():
  33. """docstring for main"""
  34. #spawn a poll of threads, and pass them queue instance
  35. for i in range(5):
  36. t = ThreadUrl(queue)
  37. t.setDaemon(True)
  38. t.start()
  39.  
  40. #populate queue with data
  41. for host in hosts:
  42. queue.put(host)
  43. #wait on the queue until everything has been processed
  44. queue.join()
  45. main()
  46. print "Elapsed Time: %s" % (time.time() - start)

  对于这个示例,有更多的代码需要说明,但与第一个线程示例相比,它并没有复杂多少,这正是因为使用了队列模块。在 Python 中使用线程时,这个模式是一种很常见的并且推荐使用的方式。具体工作步骤描述如下:

  1. 1.创建一个 Queue.Queue() 的实例,然后使用数据对它进行填充。
  2. 2.将经过填充数据的实例传递给线程类,后者是通过继承 threading.Thread 的方式创建的。
  3. 3.生成守护线程池。
  4. 4.每次从队列中取出一个项目,并使用该线程中的数据和 run 方法以执行相应的工作。
  5. 5.在完成这项工作之后,使用 queue.task_done() 函数向任务已经完成的队列发送一个信号。
  6. 6.对队列执行 join 操作,实际上意味着等到队列为空,再退出主程序。

  在使用这个模式时需要注意一点:通过将守护线程设置为 true,将允许主线程或者程序仅在守护线程处于活动状态时才能够退出。这种方式创建了一种简单的方式以控制程序流程,因为在退出之前,您可以对队列执行 join 操作、或者等到队列为空。队列模块文档详细说明了实际的处理过程,请参见参考资料

join()  保持阻塞状态,直到处理了队列中的所有项目为止。在将一个项目添加到该队列时,未完成的任务的总数就会增加。当使用者线程调用 task_done() 以表示检索了该项目、并完成了所有的工作时,那么未完成的任务的总数就会减少。当未完成的任务的总数减少到零时,join() 就会结束阻塞状态。

使用多个队列

  因为上面介绍的模式非常有效,所以可以通过连接附加线程池和队列来进行扩展,这是相当简单的。在上面的示例中,您仅仅输出了 Web 页面的开始部分。而下一个示例则将返回各线程获取的完整 Web 页面,然后将结果放置到另一个队列中。然后,对加入到第二个队列中的另一个线程池进行设置,然后对 Web 页面执行相应的处理。这个示例中所进行的工作包括使用一个名为 Beautiful Soup 的第三方 Python 模块来解析 Web 页面。使用这个模块,您只需要两行代码就可以提取所访问的每个页面的 title 标记,并将其打印输出,如“多队列数据挖掘网站”例子:

  1. #! /usr/bin/env python
  2. # coding: utf-8
  3. import Queue
  4. import threading
  5. import urllib2
  6. import time
  7. from BeautifulSoup import BeautifulSoup
  8.  
  9. hosts = ["http://yahoo.com", "http://baidu.com", "http://amazon.com","http://ibm.com", "http://apple.com"]
  10. queue = Queue.Queue()
  11. out_queue = Queue.Queue()
  12.  
  13. class ThreadUrl(threading.Thread):
  14. '''Threaded Url Grab'''
  15. def __init__(self,queue,out_queue):
  16. threading.Thread.__init__(self)
  17. self.queue = queue
  18. self.out_queue = out_queue
  19.  
  20. def run(self):
  21. """grabs host from Queue"""
  22. host = self.queue.get()
  23. #grabs urls of hosts and then grabs chunk of webpage
  24. url = urllib2.urlopen(host)
  25. chunk = url.read()
  26. #place chunk into out_queuet
  27. self.out_queue.put(chunk)
  28. #signals to queue job is done
  29. self.queue.task_done()
  30.  
  31. class DatamineThread(threading.Thread):
  32. '''Thread Url Grab'''
  33. def __init__(self, out_queue):
  34. threading.Thread.__init__(self)
  35. self.out_queue = out_queue
  36. def run(self):
  37. """grabs host from queue"""
  38. chunk = self.out_queue.get()
  39.  
  40. #parse the chunk
  41. soup = BeautifulSoup(chunk)
  42. print soup.findAll(['title'])
  43.  
  44. #signals to queue job is done
  45. self.out_queue.task_done()
  46.  
  47. start = time.time()
  48. def main():
  49. #spawn a pool of threads, and pass them queue instance
  50. for i in range(5):
  51. t = ThreadUrl(queue,out_queue)
  52. t.setDaemon(True)
  53. t.start()
  54.  
  55. #populate queue with data
  56. for host in hosts:
  57. queue.put(host)
  58.  
  59. for i in range(5):
  60. dt = DatamineThread(out_queue)
  61. dt.setDaemon(True)
  62. dt.start()
  63.  
  64. # wait on the queue until everything has been processed
  65. queue.join()
  66. out_queue.join()
  67.  
  68. main()
  69. print "Elapsed Time: %s" % (time.time()-start)

  分析这段代码时您可以看到,我们添加了另一个队列实例,然后将该队列传递给第一个线程池类 ThreadURL。接下来,对于另一个线程池类 DatamineThread,几乎复制了完全相同的结构。在这个类的 run 方法中,从队列中的各个线程获取 Web 页面、文本块,然后使用 Beautiful Soup 处理这个文本块。在这个示例中,使用 Beautiful Soup 提取每个页面的 title 标记、并将其打印输出。可以很容易地将这个示例推广到一些更有价值的应用场景,因为您掌握了基本搜索引擎或者数据挖掘工具的核心内容。一种思想是使用 Beautiful Soup 从每个页面中提取链接,然后按照它们进行导航。

总结

  本文研究了 Python 的线程,并且说明了如何使用队列来降低复杂性和减少细微的错误、并提高代码可读性的最佳实践。尽管这个基本模式比较简单,但可以通过将队列和线程池连接在一起,以便将这个模式用于解决各种各样的问题。在最后的部分中,您开始研究如何创建更复杂的处理管道,它可以用作未来项目的模型。参考资料部分提供了很多有关常规并发性和线程的极好的参考资料。

最后,还有很重要的一点需要指出,线程并不能解决所有的问题,对于许多情况,使用进程可能更为合适。特别是,当您仅需要创建许多子进程并对响应进行侦听时,那么标准库子进程模块可能使用起来更加容易。有关更多的官方说明文档,请参考参考资料部分。

文章地址:http://www.ibm.com/developerworks/cn/aix/library/au-threadingpython/

Python 多线程学习(转)的更多相关文章

  1. python多线程学习(一)

    python多线程.多进程 初探 原先刚学Java的时候,多线程也学了几天,后来一直没用到.然后接触python的多线程的时候,貌似看到一句"python多线程很鸡肋",于是乎直接 ...

  2. python多线程学习记录

    1.多线程的创建 import threading t = t.theading.Thread(target, args--) t.SetDeamon(True)//设置为守护进程 t.start() ...

  3. python 多线程学习小记

    python对于thread的管理中有两个函数:join和setDaemon setDaemon:如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.set ...

  4. python多线程学习二

    本文希望达到的目标: 多线程同步原语:互斥锁 多线程队列queue 线程池threadpool 一.多线程同步原语:互斥锁 在多线程代码中,总有一些特定的函数或者代码块不应该被多个线程同时执行,通常包 ...

  5. Python多线程学习

    一.Python中的线程使用: Python中使用线程有两种方式:函数或者用类来包装线程对象. 1.  函数式:调用thread模块中的start_new_thread()函数来产生新线程.如下例: ...

  6. python 多线程学习

    多线程(multithreaded,MT),是指从软件或者硬件上实现多个线程并发执行的技术 什么是进程? 计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据.它们只有在被读取到内存中,被操作系 ...

  7. Python多线程学习资料1

    一.Python中的线程使用: Python中使用线程有两种方式:函数或者用类来包装线程对象. 1.  函数式:调用thread模块中的start_new_thread()函数来产生新线程.如下例: ...

  8. Python多线程学习笔记

    Python中与多线程相关的模块有 thread, threading 和 Queue等,thread 和threading模块允许程序员创建和管理线程.thread模块提供了基本的线程和锁的支持,而 ...

  9. 《转》Python多线程学习

    原地址:http://www.cnblogs.com/tqsummer/archive/2011/01/25/1944771.html 一.Python中的线程使用: Python中使用线程有两种方式 ...

随机推荐

  1. Oracle ->> 生成测试数据

    declare v_exists_table number; begin select count(*) into v_exists_table from all_tables where table ...

  2. 短信发送AZDG加密算法

    public static string passport_encrypt(string txt, string key)         {             //   使用随机数发生器产生  ...

  3. QQ2013手工去广告

    QQ的广告令人讨厌,虽然网上有很多去广告补丁或者是去广告版,但是总是害怕有被盗号的风险,那除了付费会员还有其他什么方法可以安全的去除qq广告吗?显然有,那就是手动去广告. 很简单,不会比使用去广告补丁 ...

  4. INDIGO STUDIO神器!快速创建WEB、移动应用的交互原型工具【转】

    转自:http://www.uisdc.com/indigo-studio-wireframe-interactive-uis 这套最新的设计工具出自Indigo工作室,永久免费,有mac版本和WIN ...

  5. BZOJ 2228 礼物(gift)(最大子长方体)

    题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2228 题意:给出一个只含有NP两种字母的长方体.从中找出只含有字母N的长方体,造型为a* ...

  6. BISTU-(1)-4-17-2016

    A:贪心,遍历每次维护一个最便宜的价格,假如当前价格不如此前价格,就用此前价格购买当前数量的肉,每次更新最便宜的价格. #include <algorithm> #include < ...

  7. hadoop优点和缺点

    l扩容能力(Scalable):能可靠地(reliably)存储和处理千兆字节(PB)数据. l成本低(Economical):可以通过普通机器组成的服务器群来分发以及处理数据.这些服务器群总计可达数 ...

  8. website architecture

    如果在不仔细考虑网站架构的情况下就去做一个网站,这就像在没有规划行程前而去贸然旅行.你可能最终到了你的目的终点,但是你可能也不知道在这过程中,你已经走过了多少的弯路.做网站适用同样的道理.在开工之前, ...

  9. Qt之Tab键切换焦点顺序

    简介 Qt的窗口部件按用户的习惯来处理键盘焦点.也就是说,其出发点是用户的焦点能定向到任何一个窗口,或者窗口中任何一个部件. 焦点获取方式比较多,例如:鼠标点击.Tab键切换.快捷键.鼠标滚轮等. 习 ...

  10. Quickhit快速击键

    一.项目分析 根据输入速率和正确率将玩家分为不同等级,级别越高,一次显示的字符数越多,玩家正确输入一次的得分也越高.如果玩家在规定时间内完成规定次数的输入,正确率达到规定要求,则玩家升级.玩家最高级别 ...