在 Python 中使用多线程只要用 threading 模块就能达成,建立一个线程的方法是用 threading.Thread() 类别来建立一个线程对象:
import threading
t = threading.Thread(target=函式名称, args = (参数,))
如果要执行的函式需传入参数的话,必须放在 threading.Thread() 里的 args 参数之中,而不是直接放在函式名称之后。另外由于 args 接受的型别为 tuple,因此如果只有一个参数,后面记得家逗号,例如:(5,)。
要让线程开始执行就用线程对象的 start() 方法。例如:
t.start()
我们可以开启很多个线程,每个线程可称呼为子线程,让它们同步执行,而主程序则称为主线程,子线程执行工作时,主线程也会继续执行自己的工作,因此会有多个线程在执行。
如果我们想让主程序先站暂停,等待某些子线程执行结束才继续往下执行,就要使用 join() 方法,当主程序执行子线程的 join() 方法后,主程序会先暂停,等待该子线程结束后才会继续往下执行:
import threading
def job(num):
print("子线程:", num)
threads = []
for i in range(3):
threads.append(threading.Thread(target=job, args=(i,)))
threads[i].start()
for i in range(3)
print("主程序:", i)
for t in threads:
t.join()
print("结束")
竞速状况(Race condition)
当多线程之间有共享数据时,就有可能发生竞速状况,造成与预期不符的结果,例如:a, b 线程分别对变量 var 同时进行 +20 与 -1 的动作:
import threading
def sub():
global var
print("sub (before):", var)
var -= 1
print("sub (after):", var)
print("-------")
def add():
global var
var += 20
var = 10
for i in range(30):
a = threading.Thread(target=sub).start()
b = threading.Thread(target=add).start()
执行后会出现 sub() 函式对 var 减 1 后,却比原来的值还大的情况:
这是因为在印出字符串〝sub (after): 560〞之前,add() 方法抢先对 var 进行 +20 动作。
要解决竞速问题,我们可以建立 Lock 锁对象:
锁物件 = threading.Lock()
使用锁对象的 acquire() 与 release() 方法可以让线程取得钥匙与释放钥匙。因为每个锁对象只有一把钥匙,所以每次只能有一个线程能取得钥匙,其他线程若想要取得钥匙,就必须排队等待,直到拿到钥匙的线程释放钥匙后,下一个线程才能取得钥匙。
可以想象在 acquire() 与 release() 之间的区域就像是一个需要钥匙才能进入的房间,而门的钥匙只有一把,必须等到拿钥匙的线程结束在房间内的工作后,再把钥匙释放给下一个线程,其他没拿到钥匙的线程只能在房间门口继续等待钥匙。
以下将程序加上锁对象的使用,避免竞速状况:
import threading
lock = threading.Lock() # 建立锁对象
def sub():
global var
#---------------- 锁定区域 A -----------------
lock.acquire() # 进行锁定
print('sub (before):', var)
var -= 1
print('sub (after):', var)
print('------------------')
lock.release() # 释放锁定
#---------------- 锁定区域 A -----------------
def add():
global var
#---------------- 锁定区域 B -----------------
lock.acquire() # 进行锁定
var += 20
lock.release() # 释放锁定
#---------------- 锁定区域 B -----------------
var = 10
for i in range(30):
a = threading.Thread(target=sub).start()
b = threading.Thread(target=add).start()
如此一来当线程 a 取得钥匙,在锁定区域 A 对变量 var 进行 -1 时,其他线程都无法进入锁定区域 A、B,因此也无法改变 var 值。
参考:
《Python 技术者们实践!》施威铭研究室 着,旗标出版社