>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:31:07
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

4.11. 使用threading模块编写多线程程序

G1L的存在使得Python多线程编程暂时无法充分利用多处理器的优势,这种限制也许使很多人感到沮丧,但事实上这并不意味着我们需要放弃多线程。的确,对于只含纯Python 的代码也许使用多线程并不能提高运行速率,但在以下几种情况,如等待外部资源返回,或 者为了提高用户体验而建立反应灵活的用户界面,或者多用户应用程序中,多线程仍然是 一个比较好的解决方案。Python为多线程编程提供了两个非常简单明了的模块:threadthreading。那么,这两个模块在多线程处理上有什么区別呢?简单一点说:thread模块提供了多线程底层支持模块,以低级原始的方式来处理和控制线程,使用起来较为复杂;而 threading模块基于thread进行包装,将线程的操作对象化,在语言层面提供了丰富的特性。Python多线程支持用两种方式来创建线程:一种是通过继承Thread类,重写它的run()方法(注意,不是start()方法);另一种是创建一个threading.Thread对象,在它的初始化函数(__init__()) 中将可调用对象作为参数传人。实际应用中,推荐优先使用threadng模块而不是thread模块,(除非有特殊需要)。下面来具体分析一下这么做的原因。

1)threading模块对同步原语的支持更为完善和丰富。就线程的同步和互斥来说,thread 模块只提供了一种锁类型thread.LockType,而threading模块中不仅有Lock指令锁、RLock 可重人指令锁,还支持条件变量Condition、信号量SemaphoreBoundedSemaphore以及 Event事件等。

2)threading模块在主线程和子线程交互上更为友好,threading中的join()方法能够阻 塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout (可选参数)。 利用该方法可以方便地控制主线程和子线程以及子线程之间的执行。来看一个简单示例:

>>> import threading, time,sys
>>> class test(threading.Thread):
>>>     def __init__(self,name,delay):
>>>         threading.Thread.__init__(self)
>>>         self.name = name
>>>         self.delay = delay
>>>     def run(self):
>>>         print( "%s delay for %s" %(self.name,self.delay))
>>>         time.sleep(self.delay)
>>>         c = 0
>>>         while True:
>>>             print ("This is thread %s on line %s" %(self.name,c) )
>>>             c = c + 1
>>>             if c == 3:
>>>                 print ("End of thread %s" % self.name)
>>>                 break
>>>
>>> tl = test('Thread 1', 2)
>>> t2 = test('Thread 2', 2)
>>> tl.start()
>>> print ("Wait tl to end")
>>> tl.join()
>>> t2.start()
>>> print ('End of main')
Thread 1 delay for 2Wait tl to end

This is thread Thread 1 on line 0
This is thread Thread 1 on line 1
This is thread Thread 1 on line 2
End of thread Thread 1
Thread 2 delay for 2
End of main

上面的例子中,主线程main在t1上使用 join()的方法,主线程会等待tl结束后才继续运行后面的语句,由于线程t2的启动在join语句 之后,t2—直等到t1退出后才会开始运行。输出结果如图4-6所示。

  1. thread模块不支持守护线程。 thread模块中主线程退出的时候,所有的子线程不论是否还在工作,都会被强制结束,并且没有任何警告也没有任何退出前的清理工作。来看一个例子:

>>> from _thread import start_new_thread
>>> import time
>>> def myfunc(a,delay):
>>>     print ("I will calculate square of %s after delay for %s" %(a,delay))
>>>     time.sleep(delay)
>>>     print ("calculate begins...")
>>>     result = a*a
>>>     print (result)
>>>     return result
>>> start_new_thread(myfunc, (2,5))# 同时启动两个线程
>>> start_new_thread(myfunc, (6,8))
>>> time.sleep(1)
I will calculate square of 2 after delay for 5
I will calculate square of 6 after delay for 8
calculate begins...
4
calculate begins...
36

运行程序,输出结果如下,你会发现子线程的结果还未返回就已经结束了。

I will calculate square of 2 after delay for 5

I will calculate square of 6 after delya for 2

这是一种非常野蛮的主线程和子线程的交互方式。如果把主线程和子线程组成的线程组比作一个团队的话,那么主线程应该是这个团队的管理者,它了解每个线程所做的事情、所需的数据输人以及子线程结束时的输出,并把各个线程的输出组合形成有意义的结果。如果一个团队中管理者采取这种强硬的管理方式,相信很多下属都会苦不堪言,因为不仅没有被尊重的感觉,而且还有可能因为这种强势带来决策上的失误。实际上很多情况下我们可能希望主线程能够等待所有子线程都完成时才退出,这时应该使用threading模块,它支持守护线 程,可以通过setDaemon()函数来设定线程的daemon属性。当daemon厲性设置为True的 时候表明主线程的退出可以不用等待子线程完成。默认情况下,daemon标志为False,所有 的非守护线程结束后主线程才会结束。来看具体的例子,当daemon属性设置为Fake,默认 主线程会等待所有子线程结束才会退出。将t2的daemon属性改为True之后即使t2运行未 结束主线程也会直接退出。

>>> import threading
>>> import time
>>>
>>>
>>> def myfunc(a, delay):
>>>     print("I will calculate square of %s after delay for % s" % (a, delay))
>>>     time.sleep(delay)
>>>     print("calculate begins...")
>>>     result = a * a
>>>     print(result)
>>>     return result
>>>
>>>
>>> t1 = threading.Thread(target=myfunc, args=(2, 5))
>>> t2 = threading.Thread(target=myfunc, args=(6, 8))
>>> time.sleep(10)
>>> print(t1.isDaemon())
>>> print(t2.isDaemon())
>>> t2.setDaemon(True)  # 设置守护线程
>>>
>>> t1.start()
>>> t2.start()
False
False
I will calculate square of 2 after delay for 5
I will calculate square of 6 after delay for 8
calculate begins...
4
calculate begins...
36
  1. Python3中已经不存在thread模块。thread模块在Python3中被命名为_thread,这种 更改主要是为了更进一步明确表示与thread模块相关的更多的是具体的实现细节,它更多展 示的是操作系统层面的原始操作和处理。在一般的代码中不应该选择thread模块。