GIL 保护 Python 内部。这意味着:
- 您不必担心解释器中由于多线程而出错
- 大多数事情并不是真正并行运行,因为Python代码由于GIL而顺序执行
但GIL不保护你自己的代码。例如,如果您有以下代码:
self.some_number += 1
这将读取的值self.some_number
, 计算some_number+1
然后写回self.some_number
.
如果在两个线程中执行此操作,一个线程和另一个线程的操作(读、添加、写)可能会混合,从而导致结果错误。
这可能是执行顺序:
- 线程1读取
self.some_number
(0)
- 线程2读取
self.some_number
(0)
- 线程1计算
some_number+1
(1)
- 线程2计算
some_number+1
(1)
- 线程 1 写入 1
self.some_number
- 线程2写入1
self.some_number
您可以使用锁来强制执行此顺序:
- 线程1读取
self.some_number
(0)
- 线程1计算
some_number+1
(1)
- 线程 1 写入 1
self.some_number
- 线程2读取
self.some_number
(1)
- 线程2计算
some_number+1
(2)
- 线程2写入2到
self.some_number
编辑:让我们用一些显示解释行为的代码来完成这个答案:
import threading
import time
total = 0
lock = threading.Lock()
def increment_n_times(n):
global total
for i in range(n):
total += 1
def safe_increment_n_times(n):
global total
for i in range(n):
lock.acquire()
total += 1
lock.release()
def increment_in_x_threads(x, func, n):
threads = [threading.Thread(target=func, args=(n,)) for i in range(x)]
global total
total = 0
begin = time.time()
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print('finished in {}s.\ntotal: {}\nexpected: {}\ndifference: {} ({} %)'
.format(time.time()-begin, total, n*x, n*x-total, 100-total/n/x*100))
有两个函数实现增量。一个使用锁,另一个不使用锁。
功能increment_in_x_threads
实现在多个线程中并行执行递增函数。
现在使用足够多的线程运行它几乎可以肯定会发生错误:
print('unsafe:')
increment_in_x_threads(70, increment_n_times, 100000)
print('\nwith locks:')
increment_in_x_threads(70, safe_increment_n_times, 100000)
就我而言,它打印了:
unsafe:
finished in 0.9840562343597412s.
total: 4654584
expected: 7000000
difference: 2345416 (33.505942857142855 %)
with locks:
finished in 20.564176082611084s.
total: 7000000
expected: 7000000
difference: 0 (0.0 %)
因此,如果没有锁,就会出现很多错误(33% 的增量失败)。另一方面,使用锁则速度慢 20 倍。
当然,这两个数字都被放大了,因为我使用了 70 个线程,但这显示了总体思路。