Python 如果我们有GIL,为什么我们需要线程锁?
我相信这是个愚蠢的问题,但我还是找不到。实际上,最好将其分为两个问题: 1) 我说的对吗,我们可以有很多线程,但是因为GIL,一瞬间只有一个线程在执行Python 如果我们有GIL,为什么我们需要线程锁?,python,multithreading,Python,Multithreading,我相信这是个愚蠢的问题,但我还是找不到。实际上,最好将其分为两个问题: 1) 我说的对吗,我们可以有很多线程,但是因为GIL,一瞬间只有一个线程在执行 2) 如果是这样,为什么我们还需要锁?我们使用锁来避免两个线程试图读/写某个共享对象的情况,因为GIL twi线程不能在一瞬间执行,不是吗?GIL阻止多个线程同时执行,但并非在所有情况下都是如此 在线程执行I/O操作期间,会临时释放GIL。这意味着,多个线程可以同时运行。这就是你仍然需要锁的原因之一 我不知道我在哪里找到了这个参考。。。。在视频或
2) 如果是这样,为什么我们还需要锁?我们使用锁来避免两个线程试图读/写某个共享对象的情况,因为GIL twi线程不能在一瞬间执行,不是吗?GIL阻止多个线程同时执行,但并非在所有情况下都是如此 在线程执行I/O操作期间,会临时释放GIL。这意味着,多个线程可以同时运行。这就是你仍然需要锁的原因之一 我不知道我在哪里找到了这个参考。。。。在视频或其他东西中-很难找到它,但你可以进一步调查自己 更新: 我得到的几个反对的信号告诉我,人们认为内存不是一个足够好的参考,谷歌也不是一个足够好的数据库。虽然我不同意这一点,但让我提供我第一次查找(并检查!)的URL之一,这样不喜欢我答案的人就可以从此过上幸福的生活:
GIL保护Python内部。这意味着:
self.some_number += 1
这将读取self.some\u number
的值,计算some\u number+1
,然后将其写回self.some\u number
如果在两个线程中执行此操作,则一个线程和另一个线程的操作(读取、添加、写入)可能会混合,因此结果是错误的
这可能是执行顺序:
某个\u数+1
(1)某个数字+1
(1)self.some\u number
某个\u数+1
(1)self.some\u number
某个\u数+1
(2)self.some\u number
increment\u
在多个线程中并行执行递增函数
现在,使用足够多的线程运行此程序几乎可以肯定会发生错误:
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个线程,但这显示了总体思路。在任何时候,是的,只有一个线程正在执行Python代码(其他线程可能正在执行某些IO、NumPy等)。这基本上是正确的。然而,在任何单处理器系统上都是如此,然而人们仍然需要单处理器系统上的锁 请看下面的代码:
queue = []
def do_work():
while queue:
item = queue.pop(0)
process(item)
只要一根线,一切都很好。对于两个线程,您可能会从queue.pop()
中获得异常,因为另一个线程首先在最后一个项目上调用queue.pop()
。所以你需要设法解决这个问题。使用锁是一个简单的解决方案。您还可以使用适当的并发队列(如在队列
模块中)——但如果查看队列
模块内部,您会发现队列
对象内部有一个线程.Lock()
。因此,无论哪种方式,您都在使用锁
在没有必要的锁的情况下编写多线程代码是新手常见的错误。您查看代码并想,“这将很好地工作”,然后在数小时后发现,由于线程没有正确同步,发生了一些真正奇怪的事情
或者简言之,在多线程程序中有许多地方,在应用完某些更改之前,需要防止另一个线程修改结构。这允许您维护数据上的不变量,如果您不能维护不变量,则基本上不可能编写正确的代码
或者用最短的方式说,“如果你不在乎你的代码是否正确,你就不需要锁。”GIL不能保护你不修改你从不同线程并发访问的对象的内部状态,这意味着如果你不采取措施,你仍然可以把事情搞砸 因此,尽管两个线程可能不会在同一时间运行,但它们仍然可以尝试操纵对象的内部状态(一次一个,间歇性),如果不阻止这种情况发生(使用某种锁定机制),代码可能/最终会失败
注意。GIL是CPython的一个实现细节,所以你不应该依赖它。试图找到一个案例,第一个案例可能是严重错误的,看起来如果我们在两个线程中注册两个新用户,并且我们想要增加计数器,在第一个案例中,其中一个用户将无法计数。谢谢因此,我们可以说python中的线程在使用和编程设计模式方面与任何其他语言都一样,尽管有GIL,不是吗?很好的例子@zvone。艾尔
queue = []
def do_work():
while queue:
item = queue.pop(0)
process(item)