当前位置:嗨网首页>书籍在线阅读

16-多线程

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

17.6 多线程

为了引入多线程的概念,让我们来看一个例子。假设你想安排一些代码在特定时间运行,那么可以在程序启动时添加如下代码:

import time, datetime
startTime = datetime.datetime(2029, 10, 31, 0, 0, 0)
while datetime.datetime.now() < startTime:
    time.sleep(1)
print('Program now starting on Halloween 2029')
--snip--

这段代码指定2029年10月31日作为开始时间,从当前时刻开始不断调用 time.sleep(1) ,直到开始时间 startTime 。在等待 time.sleep() 的循环调用完成时,程序不能做任何事情,它只是待在那里,直到2029年万圣节。这是因为Python程序在默认情况下只有一个执行线程。

要理解什么是执行线程,就要回忆第2章关于控制流的讨论,当时你想象程序的执行就像把手指放在一行代码上,然后移动到下一行或是控制流语句让它去的任何地方。“单线程”程序只有一个“手指”。但“多线程”程序有多个“手指”。每个“手指”仍然移动到控制流语句定义的下一行代码,但这些“手指”可以在程序的不同地方同时执行不同的代码行。(到目前为止,本书所有的程序都是单线程的。)

不必让所有的代码等待 time.sleep() 函数完成后再执行,你可以使用Python的 threading 模块以在单独的线程中执行延迟或安排的代码。这个单独的线程将因为 time.sleep() 调用而暂停。同时,程序可以在原来的线程中做其他工作。

要得到单独的线程,首先要调用 threading.Thread() 函数来生成一个 Thread 对象。在新的文件中输入以下代码,并保存为threadDemo.py:

   import threading, time
   print('Start of program.')
❶ def takeANap():
        time.sleep(5)
        print('Wake up!')
❷ threadObj = threading.Thread(target=takeANap)
❸ threadObj.start()
   print('End of program.')

在❶行,我们定义了一个希望用于新线程的函数。为了创建一个 Thread 对象,我们调用 threading.Thread() ,并传入关键字参数 target=takeANap ❷。这意味着我们要在新线程中调用的函数是 takeANap() 。请注意,关键字参数是 target=takeANap ,而不是 target=takeANap() 。这是因为你想将 takeANap() 函数本身作为参数,而不是调用 takeANap() 并传入它的返回值。

我们将 threading.Thread() 创建的 Thread 对象保存在 threadObj 中,然后调用 threadObj.start() ❸来创建新的线程,并开始在新线程中执行目标函数。如果运行该程序,输出结果将像这样:

Start of program.
End of program.
Wake up!

这可能有点令人困惑。如果 print('End of program.') 是程序的最后一行,你可能会认为它是最后输出的内容。 Wake up! 在它后面,是因为当 threadObj. start() 被调用时, threadObj 的目标函数在一个新的执行线程中运行。将它看成出现在 takeANap() 函数开始处第二根“手指”。主线程继续执行 print('End of program.') 。同时,新线程已执行了 time.sleep(5) 调用,暂停5秒。之后它从5秒“小睡”中醒来,输出了 'Wake up!' ,然后从 takeANap() 函数返回。按时间顺序, 'Wake up!' 是程序最后输出的内容。

通常,程序在文件的最后一行代码执行后终止(或调用 sys.exit() )。但threadDemo.py 有两个线程。第一个是最初的线程,从程序开始处开始,在执行 print('End of program.') 后结束。第二个线程是调用 threadObj.start() 时创建的,始于 takeANap() 函数的开始处,在 takeANap() 返回后结束。

在程序的所有线程终止之前,Python程序不会终止。在运行threadDemo.py时,即使最初的线程已经终止,第二个线程仍然执行 time.sleep(5) 调用。