banner

一文学会 Python 多线程编程

作者: afenxi来源: afenxi时间:2016-12-03 13:56:05

摘要:Threading 模块从 Python 1.5.2 版开始出现,用于增强底层的多线程模块 thread 。Threading 模块让操作多线程变得更简单,并且支持程序同时运行多个操作。

一文学会 Python 多线程编程-数据分析网

Threading 模块从 Python 1.5.2 版开始出现,用于增强底层的多线程模块 thread 。Threading 模块让操作多线程变得更简单,并且支持程序同时运行多个操作。

注意,Python 中的多线程最好用于处理有关 I/O 的操作,如从网上下载资源或者从本地读取文件或者目录。如果你要做的是 CPU 密集型操作,那么你需要使用 Python 的 multiprocessing 模块。这样做的原因是,Python 有一个全局解释器锁 (GIL),使得所有子线程都必须运行在同一个主线程中。正因为如此,当你通过多线程来处理多个 CPU 密集型任务时,你会发现它实际上运行的更慢。因此,我们将重点放在那些多线程最擅长的领域:I/O 操作!

线程简介

多线程能让你像运行一个独立的程序一样运行一段长代码。这有点像调用子进程(subprocess),不过区别是你调用的是一个函数或者一个类,而不是独立的程序。在我看来,举例说明更有助于解释。下面来看一个简单的例子:

import threading def doubler(number): """ 可以被线程使用的一个函数 """ print(threading.currentThread().getName() + n) print(number * 2) print() if __name__ == __main__: for i in range(5): my_thread = threading.Thread(target=doubler, args=(i,)) my_thread.start()

这里,我们导入 threading 模块并且创建一个叫 doubler 的常规函数。这个函数接受一个值,然后把这个值翻一番。它还会打印出调用这个函数的线程的名称,并在最后打印一行空行。然后在代码的最后一块,我们创建五个线程并且依次启动它们。在我们实例化一个线程时,你会注意到,我们把 doubler 函数传给 target 参数,同时也给 doubler 函数传递了参数。Args 参数看起来有些奇怪,那是因为我们需要传递一个序列给 doubler 函数,但它只接受一个变量,所以我们把逗号放在尾部来创建只有一个参数的序列。

需要注意的是,如果你想等待一个线程结束,那么需要调用 join() 方法。

当你运行以上这段代码,会得到以下输出内容:

Thread-1 0 Thread-2 2 Thread-3 4 Thread-4 6 Thread-5 8

当然,通常情况下你不会希望输出打印到标准输出。如果不幸真的这么做了,那么最终的显示效果将会非常混乱。你应该使用 Python 的 logging 模块。它是线程安全的,并且表现出色。让我们用logging 模块修改上面的例子并且给我们的线程命名。代码如下:

import logging import threading def get_logger(): logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG) fh = logging.FileHandler("threading.log") fmt = %(asctime)s - %(threadName)s - %(levelname)s - %(message)s formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) return logger def doubler(number, logger): """ 可以被线程使用的一个函数 """ logger.debug(doubler function executing) result = number * 2 logger.debug(doubler function ended with: .format(data)) processed = data * 2 print(processed) evt.set() q.task_done() if __name__ == __main__: q = Queue() data = [5, 10, 13, -1] thread_one = threading.Thread(target=creator, args=(data, q)) thread_two = threading.Thread(target=my_consumer, args=(q,)) thread_one.start() thread_two.start() q.join()

让我们掰开揉碎分析一下。首先,我们有一个创建者(creator)函数(亦称作生产者(producer)),我们用它来创建想要操作(或者消费)的数据。然后用另外一个函数my_consumer 来处理刚才创建出来的数据。Creator 函数使用 Queue 的 put 方法向队列中插入数据,消费者将会持续不断的检测有没有更多的数据,当发现有数据时就会处理数据。Queue 对象处理所有的获取锁和释放锁的过程,这些不用我们太关心。

在这个例子中,先创建一个列表,然后创建两个线程,一个用作生产者,一个作为消费者。你会发现,我们给两个线程都传递了 Queue 对象,这两个线程隐藏了关于锁处理的细节。队列实现了数据从第一个线程到第二个线程的传递。当第一个线程把数据放入队列时,同时也传递一个 Event 事件,紧接着挂起自己,等待该事件结束。在消费者侧,也就是第二个线程,则做数据处理工作。当完成数据处理后就会调用 Event 事件的 set 方法,通知第一个线程已经把数据处理完毕了,可以继续生产了。

最后一行代码调用了 Queue 对象的 join 方法,它会告知 Queue 等待所有线程结束。当第一个线程把所有数据都放到队列中,它也就运行结束了。

结束语

以上涵盖了关于线程的诸多方面,主要包括:

线程基础知识 锁的工作方式 什么是事件以及如何使用 如何使用定时器 通过 Queues/Events 实现线程间通信

现在你们知道如何使用线程以及线程擅长什么了,希望在你们的代码中能有它们的用武之地。

本文作者为 Michael Driscool,是其新书 Python 201 的一节。本文译者为 linkcheng,译者简介:linkcheng,专业电子信息工程。已有两年工作经验,从事 c/c++ 开发。目前在学习 flask,希望以后自己可以搭建网站。

banner
看过还想看
可能还想看
最新文章
Yonghong Z-Suite一站式大数据分析平台 —— 以卓越的数据技术为客户创造价值,实现客户成功。