python并发:对线程的介绍
Python有大量不同的并发模块,例如threading,queues和multiprocessing。threading模块是实现并发的主要途径。几年前,multiprocessing模块被加到python的标准库中。但这篇文章集中讨论threading模块。
入门
我们先用一个简单例子来说明线程是如何工作的。我们创建一个Thread类的子类,并把它的名字输出到标准输出(stdout)里。让我们开始吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import random import time from threading import Thread ######################################################################## class MyThread(Thread): """ A threading example """ #---------------------------------------------------------------------- def __init__(self, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.start() #---------------------------------------------------------------------- def run(self): """Run the thread""" amount = random.randint(3, 15) time.sleep(amount) msg = "%s has finished!" % self.name print(msg) #---------------------------------------------------------------------- def create_threads(): """ Create a group of threads """ for i in range(5): name = "Thread #%s" % (i+1) my_thread = MyThread(name=name) if __name__ == "__main__": create_threads() |
在上面的代码中,我们引入了Python的random模块,time模块并且从threading模块中引入了Thread类。接着创建一个Thread类的子类,并重写它的__init__方法,让其接收一个名为name的参数。为了开启一个线程,你必须调用它的start()方法,所以我们在init方法最后调用它。当你开启一个线程时,它会自动调用run()方法。我们重写了它的run方法,让它选择一个随机时间数去休眠。例子中的random.randint让python随机选择3到15之间的一个数,然后我们让线程休眠这么多秒来模拟线程在做一些事情。最后我们将线程名称打印出来让用户知道线程已经完成。
create_threads方法创建了5个线程,分别给了独一无二的名称。如果你运行这段代码,你将看到类似如下的结果:
1 2 3 4 5 |
Thread #2 has finished! Thread #1 has finished! Thread #3 has finished! Thread #4 has finished! Thread #5 has finished! |
每次输出的顺序都会不同。试着运行几次代码观察顺序的变化。
写一个多线程的下载器
相对之前的例子,一个工具程序能更好地解释线程是如何工作的。所以在这个例子中,我们创建了一个可以从网上下载文件的Thread类。美国国税局有大量给美国公民用来记税的PDF表单。我们用这个免费的资源来举例,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# Use this version for Python 2 import os import urllib2 from threading import Thread ######################################################################## class DownloadThread(Thread): """ A threading example that can download a file """ #---------------------------------------------------------------------- def __init__(self, url, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.url = url #---------------------------------------------------------------------- def run(self): """Run the thread""" handle = urllib2.urlopen(self.url) fname = os.path.basename(self.url) with open(fname, "wb") as f_handler: while True: chunk = handle.read(1024) if not chunk: break f_handler.write(chunk) msg = "%s has finished downloading %s!" % (self.name, self.url) print(msg) #---------------------------------------------------------------------- def main(urls): """ Run the program """ for item, url in enumerate(urls): name = "Thread %s" % (item+1) thread = DownloadThread(url, name) thread.start() if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] main(urls) |
这基本上是对第一版的完全重写。在这个例子中,我们引用了os、urllib2和threading模块。我们在thread类里使用urllib2模块来下载文件。使用os模块提取下载文件的文件名,这样我们可以在自己电脑上创建同名文件。在DownloadThread类中,我们让__init__接收一个url和一个线程名。在run方法中,我们访问url,抽取文件名,并使用该文件名在磁盘上命名或创建一个文件。然后我们使用while循环以每次1KB的速度下载文件并把它写进磁盘。一旦文件完成保存,我们打印出线程的名称和完成下载的url。
升级
这个代码的Python3版本略微有些不同。你必须引用urllib,并使用urllib.request.urlopen代替urllib2urlopen。这是python3的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# Use this version for Python 3 import os import urllib.request from threading import Thread ######################################################################## class DownloadThread(Thread): """ A threading example that can download a file """ #---------------------------------------------------------------------- def __init__(self, url, name): """Initialize the thread""" Thread.__init__(self) self.name = name self.url = url #---------------------------------------------------------------------- def run(self): """Run the thread""" handle = urllib.request.urlopen(self.url) fname = os.path.basename(self.url) with open(fname, "wb") as f_handler: while True: chunk = handle.read(1024) if not chunk: break f_handler.write(chunk) msg = "%s has finished downloading %s!" % (self.name, self.url) print(msg) #---------------------------------------------------------------------- def main(urls): """ Run the program """ for item, url in enumerate(urls): name = "Thread %s" % (item+1) thread = DownloadThread(url, name) thread.start() if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] main(urls) |
结语
现在你从理论和实践上了解了如何使用线程。当你创建用户界面并想保持界面的可用性时,线程就特别有用。没有线程,用户界面将变得迟钝,当你下载一个大文件或者执行一个庞大的数据库查询命令时用户界面会长时间无响应。为了防止这样情况发生,你可以使用多线程来处理运行时间长的进程并且在完成后返回界面进行交互。