Python 如果我们有GIL,为什么我们需要线程锁?

Python 如果我们有GIL,为什么我们需要线程锁?,python,multithreading,Python,Multithreading,我相信这是个愚蠢的问题,但我还是找不到。实际上,最好将其分为两个问题: 1) 我说的对吗,我们可以有很多线程,但是因为GIL,一瞬间只有一个线程在执行 2) 如果是这样,为什么我们还需要锁?我们使用锁来避免两个线程试图读/写某个共享对象的情况,因为GIL twi线程不能在一瞬间执行,不是吗?GIL阻止多个线程同时执行,但并非在所有情况下都是如此 在线程执行I/O操作期间,会临时释放GIL。这意味着,多个线程可以同时运行。这就是你仍然需要锁的原因之一 我不知道我在哪里找到了这个参考。。。。在视频或

我相信这是个愚蠢的问题,但我还是找不到。实际上,最好将其分为两个问题:

1) 我说的对吗,我们可以有很多线程,但是因为GIL,一瞬间只有一个线程在执行


2) 如果是这样,为什么我们还需要锁?我们使用锁来避免两个线程试图读/写某个共享对象的情况,因为GIL twi线程不能在一瞬间执行,不是吗?

GIL阻止多个线程同时执行,但并非在所有情况下都是如此

在线程执行I/O操作期间,会临时释放GIL。这意味着,多个线程可以同时运行。这就是你仍然需要锁的原因之一

我不知道我在哪里找到了这个参考。。。。在视频或其他东西中-很难找到它,但你可以进一步调查自己

更新:

我得到的几个反对的信号告诉我,人们认为内存不是一个足够好的参考,谷歌也不是一个足够好的数据库。虽然我不同意这一点,但让我提供我第一次查找(并检查!)的URL之一,这样不喜欢我答案的人就可以从此过上幸福的生活:
GIL保护Python内部。这意味着:

  • 您不必担心解释器由于多线程而出错
  • 大多数事情实际上并不是并行运行的,因为由于GIL,python代码是按顺序执行的
  • 但是GIL并不保护您自己的代码。例如,如果您有以下代码:

    self.some_number += 1
    
    这将读取
    self.some\u number
    的值,计算
    some\u number+1
    ,然后将其写回
    self.some\u number

    如果在两个线程中执行此操作,则一个线程和另一个线程的操作(读取、添加、写入)可能会混合,因此结果是错误的

    这可能是执行顺序:

  • thread1读取自身。某些编号(0)
  • thread2读取自身。某些编号(0)
  • thread1计算
    某个\u数+1
    (1)
  • thread2计算
    某个数字+1
    (1)
  • thread1将1写入
    self.some\u number
  • thread2将1写入self.some\u number
  • 您可以使用锁强制执行此执行顺序:

  • thread1读取自身。某些编号(0)
  • thread1计算
    某个\u数+1
    (1)
  • thread1将1写入
    self.some\u number
  • thread2读取自身编号(1)
  • thread2计算
    某个\u数+1
    (2)
  • thread2将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)