Pau*_*aul 20 python multithreading
我相信这是一个愚蠢的问题,但我仍然找不到它.实际上最好将它分成两个问题:
1)我是对的,我们可以有很多线程但是因为GIL在一瞬间只有一个线程正在执行?
2)如果是这样,为什么我们还需要锁?我们使用锁来避免两个线程试图读/写某些共享对象的情况,因为GIL twi线程无法在一瞬间执行,可以吗?
zvo*_*one 22
GIL保护Python内部.这意味着:
但GIL不保护您自己的代码.例如,如果您有此代码:
self.some_number += 1
Run Code Online (Sandbox Code Playgroud)
那将是读取self.some_number,计算some_number+1,然后写回来的价值self.some_number.
如果在两个线程中执行此操作,则一个线程和另一个线程的操作(读取,添加,写入)可能会混合,因此结果是错误的.
这可能是执行的顺序:
self.some_number(0)self.some_number(0)some_number+1(1)some_number+1(1)self.some_numberself.some_number您使用锁来强制执行此执行顺序:
self.some_number(0)some_number+1(1)self.some_numberself.some_number(1)some_number+1(2)self.some_numberimport 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))
Run Code Online (Sandbox Code Playgroud)
有两个实现增量的功能.一个使用锁,另一个不使用.
功能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)
Run Code Online (Sandbox Code Playgroud)
在我的情况下,它打印:
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 %)
Run Code Online (Sandbox Code Playgroud)
所以没有锁,就会出现很多错误(33%的增量失败).另一方面,使用锁定它的速度要慢20倍.
当然,这两个数字都被炸毁,因为我使用了70个线程,但这显示了一般的想法.
在任何时候,是的,只有一个线程正在执行 Python 代码(其他线程可能正在执行一些 IO、NumPy 等)。这大部分是正确的。然而,这在任何单处理器系统上都是微不足道的,但人们仍然需要在单处理器系统上加锁。
看看下面的代码:
queue = []
def do_work():
while queue:
item = queue.pop(0)
process(item)
Run Code Online (Sandbox Code Playgroud)
一个线程,一切都很好。对于两个线程,您可能会收到异常,queue.pop()因为另一个线程首先调用queue.pop()了最后一项。所以你需要以某种方式处理它。使用锁是一个简单的解决方案。您还可以使用适当的并发队列(如在queue模块中)——但是如果您查看queue模块内部,您会发现该Queue对象threading.Lock()内部有一个。因此,无论哪种方式,您都在使用锁。
在没有必要的锁的情况下编写多线程代码是一个常见的新手错误。您查看代码并认为“这会正常工作”,然后在几个小时后发现确实发生了一些奇怪的事情,因为线程没有正确同步。
或者简而言之,在多线程程序中有很多地方需要防止另一个线程修改结构,直到您完成一些更改。这允许您维护数据上的不变量,如果您不能维护不变量,那么基本上不可能编写正确的代码。
或者用尽可能最短的方式,“如果你不在乎你的代码是否正确,你就不需要锁。”