GIL的存在使得Python多线程编程暂时无法充分利用多处理器的优势,这种限制也许使很多人感到沮丧, 但事实上这并不意味着需要放弃多线程。的确,对于只含纯Python 的代码也许使用多线程并不能提高运行速率, 但在以下几种情况,如等待外部资源返回,或者为了提高用户体验而建立反应灵活的用户界面, 或者多用户应用程序中,多线程仍然是一个比较好的解决方案。
Python为多线程编程提供了两个非常简单明了的模块:thread
和 threading
。
那么,这两个模块在多线程处理上有什么区別呢?简单一点说:thread
模块提供了多线程底层支持模块,
以低级原始的方式来处理和控制线程,使用起来较为复杂;
而 threading
模块基于thread
进行包装,将线程的操作对象化,在语言层面提供了丰富的特性。
Python多线程支持用两种方式来创建线程:一种是通过继承Thread
类,
重写它的run()
方法(注意,不是start()
方法);另一种是创建一个threading.Thread
对象,
在它的初始化函数( __init__()
)中将可调用对象作为参数传入。
优先使用threading
模块的原因
实际应用中,推荐优先使用threading
模块而不是thread
模块,(除非有特殊需要)。
下面来具体分析一下这么做的原因。
threading
模块对同步原语的支持更为完善和丰富。就线程的同步和互斥来说,thread
模块只提供了一种锁类型thread.LockType
,而threading
模块中不仅有Lock
指令锁、RLock
可重入指令锁,还支持条件变量Condition
、信号量Semaphore
、BoundedSemaphore
以及Event
事件等。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")
Thread 1 delay for 2 Wait tl to end
tl.join()
t2.start()
print ('End of main')
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
退出后才会开始运行。输出结果如上面所示。
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 delya for 2
这是一种非常野蛮的主线程和子线程的交互方式。如果把主线程和子线程组成的线程组比作一个团队的话,
那么主线程应该是这个团队的管理者,它了解每个线程所做的事情、所需的数据输入以及子线程结束时的输出,
并把各个线程的输出组合形成有意义的结果。如果一个团队中管理者采取这种强硬的管理方式,
相信很多下属都会苦不堪言,因为不仅没有被尊重的感觉,而且还有可能因为这种强势带来决策上的失误。
实际上很多情况下可能希望主线程能够等待所有子线程都完成时才退出,这时应该使用 threading
模块,
它支持守护线程,可以通过 setDaemon()
函数来设定线程的 daemon
属性。
当 daemon
属性设置为 True
的时候表明主线程的退出可以不用等待子线程完成。
默认情况下, daemon
标志为 False
,所有的非守护线程结束后主线程才会结束。
来看具体的例子,当 daemon
属性设置为 False
,默认主线程会等待所有子线程结束才会退出。
将 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())
False
/tmp/ipykernel_5732/2076934706.py:4: DeprecationWarning: isDaemon() is deprecated, get the daemon attribute instead print(t1.isDaemon())
print(t2.isDaemon())
False
/tmp/ipykernel_5732/1916433033.py:1: DeprecationWarning: isDaemon() is deprecated, get the daemon attribute instead print(t2.isDaemon())
设置守护线程
t2.setDaemon(True)
t1.start()
t2.start()
I will calculate square of 2 after delay for 5 I will calculate square of 6 after delay for 8
/tmp/ipykernel_5732/4036813649.py:1: DeprecationWarning: setDaemon() is deprecated, set the daemon attribute instead t2.setDaemon(True)
calculate begins... 4 calculate begins... 36
- Python3中已经不存在
thread
模块。thread
模块在Python3中被命名为_thread
, 这种更改主要是为了更进一步明确表示与thread
模块相关的更多的是具体的实现细节, 它更多展示的是操作系统层面的原始操作和处理。在一般的代码中不应该选择thread
模块。