Python GIL 以及线程

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() 方法使主线程等待,直到 thread1thread2 运行结束。

3. 线程池

使用模块 concurrent.futures 里的 ThreadPoolExecutor 可以创建 thread pool, 使用线程池比使用 Thread 逐个创建线程方便。 concurrent.futuresThreadPoolExecutor 十分流行。

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 ,这通常不是件很容易就能做到的事情,所以不要杀线程。

Python中的全局解释器锁(GIL)是为了保护Python字节码的解释器核心同时执行,防止多个线程并发修改数据结构而引入的。这意味着,在任何时候,只有一个线程能够执行Python字节码,即使是在多核处理器上也是如此。因此,尽管Python支持多线程,但它并不适用于那些依赖大量CPU计算的任务(即CPU密集型),因为在这种情况下,线程切换频繁,CPU的实际利用率并没有提高。 如果你想要在Python中实现多线程,并且期望充分利用多核CPU,你需要关注以下几个方面: 1. **I/O密集型任务**:Python的`threading`库可以处理I/O密集型任务,因为在等待I/O响应时,GIL会被释放,其他线程可以执行。 2. **多进程**:Python的`multiprocessing`模块提供了一个无GIL的环境,因为它会启动新的进程,而不是共享相同的解释器实例,所以理论上可以实现真正的并行计算。 3. **使用多线程池**:如`concurrent.futures.ThreadPoolExecutor`,它可以帮助管理线程池,将任务分发给各个线程,让它们在等待I/O操作时保持忙碌。 4. **利用第三方库**:有些第三方库,如Celery或multiprocessing.dummy,虽然底层还是基于线程,但由于其工作原理,可能能在某些场景下减轻GIL的影响。 需要注意的是,虽然以上措施可以在某种程度上提升性能,但要完全绕开GIL所带来的限制,可能需要采用更底层的技术,例如Cython扩展或使用Numba进行加速。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值