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(
- result))
-
- if __name__ == '__main__':
- logger = get_logger
- thread_names = ['Mike', 'George', 'Wanda', 'Dingbat', 'Nina']
- for i in range(5):
- my_thread = threading.Thread(
- target=doubler, name=thread_names[i], args=(i,logger))
- my_thread.start
代码中最大的改变就是加入了 get_logger函数。这段代码将创建一个被设置为调试级别的日志记录器。它将日志保存在当前目录(即脚本运行所在的目录)下,然后设置每行日志的格式。格式包括时间戳、线程名、日志记录级别以及日志信息。
在 doubler 函数中,我们把 print语句换成 logging 语句。你会注发现,在创建线程时,我们给 doubler 函数传入了 logger 对象。这样做的原因是,如果在每个线程中实例化 logging 对象,那么将会产生多个 logging 单例(singleton),并且日志中将会有很多重复的内容。
最后,创建一个名称列表,然后使用 name关键字参数为每一个线程设置具体名称,这样就可以为线程命名。运行以上代码,将会得到包含以下内容的日志文件:
- 2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function executing
- 2016-07-24 20:39:50,055 - Mike - DEBUG - doubler function ended with: 0
- 2016-07-24 20:39:50,055 - George - DEBUG - doubler function executing
- 2016-07-24 20:39:50,056 - George - DEBUG - doubler function ended with: 2
- 2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function executing
- 2016-07-24 20:39:50,056 - Wanda - DEBUG - doubler function ended with: 4
- 2016-07-24 20:39:50,056 - Dingbat - DEBUG - doubler function executing
- 2016-07-24 20:39:50,057 - Dingbat - DEBUG - doubler function ended with: 6
- 2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function executing
- 2016-07-24 20:39:50,057 - Nina - DEBUG - doubler function ended with: 8
输出结果不言自明,所以继续介绍其他内容。在本节中再多说一点,即通过继承 threading.Thread实现多线程。举最后一个例子,通过继承 threading.Thread 创建子类,而不是直接调用 Thread 函数。
(编辑:ASP站长网)
|