1. GIL
GIL: Global Interpreter Lock 全局解释器锁.
每个 python process 都只创建唯一一个关键资源 GIL,当一个 thread 运行时,它必须获取此 GIL,运行结束再释放。
由于 GIL 唯一,因此任何时候,一个 process 中都不能有两个 thread 同时运行,一次只能运行一个 thread,这是由 python 的实现决定的,python 不善于以并行方式运行代码。
2. python 使用 thread 的意义: 减少等待时间
虽然 python 不支持两个 thread 并行执行,但是当程序需要等待时,使用线thread 可以缩短等待时间。
如果所有的 thread 都只是使用CPU,使用 thread 没有意义,因为时间不仅不会缩短,反而会因为 thread 频繁获取和释放 GIL 增加运行时间。
要创建线程,要先导入 threading
模块:
from threading import Thread
my_thread = Thread(target=my_func_name)
my_thread.start()
下面的程序有3个 thread: 主线程 main thread 以及 thread 1, thread 2
ask_user()
函数等待用户输入并输出,complex_calculation()
运行一项需要时间的计算:
import time
from threading import Thread
def ask_user():
start = time.time()
user_input = input("Enter your name: ")
greet = f"Hello, {user_input}"
print(greet)
print(f'ask_user, {time.time() - start}')
def complex_calculation():
start = time.time()
print('Started calculating...')
[x**2 for x in range(20000000)]
print(f'complex_calculation, {time.time() - start}')
start = time.time()
ask_user()
complex_calculation()
print(f'Single thread total time: {time.time() - start}')
thread1 = Thread(target=complex_calculation)
thread2 = Thread(target=ask_user)
start = time.time()
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"Two threads total time: {time.time() - start} ")
输出如下:
Enter your name: user
Hello, user
ask_user, 2.0858802795410156
Started calculating...
complex_calculation, 3.8278822898864746
Single thread total time: 5.914762496948242
Started calculating...
Enter your name: user
Hello, user
ask_user, 1.4944937229156494
complex_calculation, 3.8242249488830566
Two threads total time: 3.8242249488830566
Process finished with exit code 0
可见,使用线程,运行时间明显缩短,所用时间对比:
Single thread total time: 5.914762496948242
Two threads total time: 3.8242249488830566
join()
方法使主线程等待,直到 thread1
,thread2
运行结束。
3. 线程池
使用模块 concurrent.futures
里的 ThreadPoolExecutor
可以创建 thread pool, 使用线程池比使用 Thread
逐个创建线程方便。 concurrent.futures
和 ThreadPoolExecutor
十分流行。
import time
from concurrent.futures import ThreadPoolExecutor
def ask_user():
pass
def complex_calculation():
pass
start = time.time()
# max_workers=2 create 2 threads in this collection of threads
with ThreadPoolExecutor(max_workers=2) as pool:
pool.submit(complex_calculation)
pool.submit(ask_user)
# blocking operation like join(), because using with, below code no need to run
# pool.shutdown()
print(f"Two threads total time: {time.time() - start} ")
4. 不要杀线程!
不要杀线程,而是要等待,直到线程结束。
因为线程可能持有 GIL ,如果在没有释放之前线程就被杀掉,唯一的 GIL 会因此丢失,程序会因为其他线程在等待它们不可能重新获得的 GIL 而停止执行,陷入死锁。
如果一定要杀线程,就要手动释放 GIL ,这通常不是件很容易就能做到的事情,所以不要杀线程。