事实上,没错,每个线程就是一个新的解释器线程。
它是由操作系统管理的真正的线程,而不是仅在Python虚拟机内部用于Python代码的内部管理线程。
GIL的作用是防止基于操作系统的线程干扰Python对象。
想象一下,在一个CPU上有一个线程,另一个CPU上有另一个线程。纯并行的线程,用汇编语言编写。两个线程同时尝试更改寄存器值。这种情况绝对不可取。访问同一内存位置的汇编指令最终会混淆要将什么移动到哪里和何时移动。这样操作的结果最终可能很容易导致分段错误。好吧,如果我们用C语言编写,则C语言控制该部分,以使其不会在C代码中发生。GIL在C级别上对Python代码执行Python对象更改时做同样的事情。因此,在更改它们时,实现Python对象的代码不会失去原子性。想象一个线程向列表插入值,而另一个线程正在将其向下移动,因为该另一个线程从其中删除了一些元素。没有GIL,这将导致崩溃。
GIL对线程内代码的原子性没有任何作用。它仅用于内部内存管理。
即使您拥有线程安全的对象,如deque(),如果您在其中执行多个操作,则没有附加锁定,就会从另一个线程插入的某个地方获取结果。当心,问题出现了!
假设一个线程从堆栈中取出一个对象,检查一些关于它的条件,并且如果条件正确,则将其删除。
stack = [2,3,4,5,6,7,8]
def thread1 ():
while 1:
v = stack[0]
sleep(0.001)
if v%2==0: del stack[0]
sleep(0.001)
当然,这很愚蠢,应该使用stack.pop(0)来避免这种情况。但这只是一个例子。
现在让我们再开一个线程,每0.002秒向堆栈添加一个元素:
def thread2 ():
while 1:
stack.insert(0, stack[-1]+1)
sleep(0.002)
现在如果您执行以下操作:
thread(thread2,())
sleep(1)
thread(thread1,())
有一种情况,虽然不太可能发生,即thread2()试图在thread1()检索和删除之间堆叠新项。因此,thread1()将删除一个新添加的项而不是正在检查的项。结果与我们的愿望不符。因此,GIL不控制我们在线程中执行的操作,只控制更基本意义上的线程对彼此的操作。
想象一下,您编写了一个购买某个活动门票的服务器。两个用户同时连接并尝试购买同一张票。如果您不小心,用户可能会相互覆盖。
线程安全对象是执行操作的对象,它不允许另一个操作在第一个操作完成之前发生。
例如,如果您在一个线程中迭代deque(),并且在其中途另一个线程尝试附加某些内容,则append()将阻塞,直到第一个线程完成对其的迭代。这是线程安全的。