python 多线程

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

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),直到开始时间。在等待 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 秒钟小睡中醒来,打印了'Wakeup!',然后从 takeANap()函数返回。按时间顺序, 'Wake up!'是程序最后打印的内容。通常,程序在文件中最后一行代码执行后终止(或调用 sys.exit())。但threadDemo.py 有两个线程。第一个是最初的线程,从程序开始处开始,在 print('Endof program.')后结束。第二个线程是调用 threadObj.start()时创建的,始于 takeANap()函数的开始处,在 takeANap()返回后结束。在程序的所有线程终止之前, Python 程序不会终止。在运行 threadDemo.py 时,即使最初的线程已经终止,第二个线程仍然执行 time.sleep(5)调用。
 

向线程的目标函数传递参数
如果想在新线程中运行的目标函数有参数,可以将目标函数的参数传入threading.Thread()。 例如,假设想在自己的线程中运行以下 print()调用:

>>> print('Cats', 'Dogs', 'Frogs', sep=' & ')
Cats & Dogs & Frogs

该 print()调用有 3 个常规参数: 'Cats'、 'Dogs'和'Frogs',以及一个关键字参数: sep=' & '。常规参数可以作为一个列表,传递给 threading.Thread()中的 args 关键字参数。关键字参数可以作为一个字典,传递给 threading.Thread()中的 kwargs 关键字参数。在交互式环境中输入以下代码:

>>> import threading
>>> threadObj = threading.Thread(target=print, args=['Cats', 'Dogs', 'Frogs'],
kwargs={'sep': ' & '})
>>> threadObj.start()
Cats & Dogs & Frogs

为了确保参数'Cats'、 'Dogs'和'Frogs'传递给新线程中的 print(),我们将 args=['Cats','Dogs', 'Frogs']传入 threading.Thread()。为了确保关键字参数 sep=' & '传递给新线程中的 print(),我们将 kwargs={'sep': '& '}传入 threading.Thread()。threadObj.start()调用将创建一个新线程来调用 print()函数,它会传入'Cats'、'Dogs'和'Frogs'作为参数,以及' & '作为 sep 关键字参数。下面创建新线程调用 print()的方法是不正确的:

threadObj = threading.Thread(target=print('Cats', 'Dogs', 'Frogs', sep=' & '))

这行代码最终会调用 print()函数,将它的返回值(print()的返回值总是无)作为target 关键字参数。它没有传递 print()函数本身。如果要向新线程中的函数传递参数,就使用 threading.Thread()函数的 args 和 kwargs 关键字参数。
 

并发问题
可以轻松地创建多个新线程,让它们同时运行。但多线程也可能会导致所谓的并发问题。如果这些线程同时读写变量,导致互相干扰,就会发生并发问题。并发问题可能很难一致地重现,所以难以调试。多线程编程本身就是一个广泛的主题,超出了本书的范围。必须记住的是:为了避免并发问题,绝不让多个线程读取或写入相同的变量。当创建一个新的 Thread 对象时,要确保其目标函数只使用该函数中的局部变量。这将避免程序中难以调试的并发问题
在 http://nostarch.com/automatestuff/,有关于多线程编程的初学者教程。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值