在 Python 中使用多线程只要用 threading 模块就能达成,建立一个线程的方法是用 threading.Thread() 类别来建立一个线程对象:

import threading
t = threading.Thread(target=函式名称, args = (参数,))

如果要执行的函式需传入参数的话,必须放在 threading.Thread() 里的 args 参数之中,而不是直接放在函式名称之后。另外由于 args 接受的型别为 tuple,因此如果只有一个参数,后面记得家逗号,例如:(5,)。

要让线程开始执行就用线程对象的 start() 方法。例如:

t.start()

我们可以开启很多个线程,每个线程可称呼为子线程,让它们同步执行,而主程序则称为主线程,子线程执行工作时,主线程也会继续执行自己的工作,因此会有多个线程在执行。

如果我们想让主程序先站暂停,等待某些子线程执行结束才继续往下执行,就要使用 join() 方法,当主程序执行子线程的 join() 方法后,主程序会先暂停,等待该子线程结束后才会继续往下执行:

python多线程传入字符串参数 python多线程引入模块_主线程

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 后,却比原来的值还大的情况:

python多线程传入字符串参数 python多线程引入模块_子线程_02


这是因为在印出字符串〝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 技术者们实践!》施威铭研究室 着,旗标出版社