由于GIL,在multithreadingPython代码中锁是不必要的?

如果您依赖于具有全局解释器locking(即CPython)的Python的实现并编写multithreading代码,那么您是否真的需要locking?

如果GIL不允许多个指令并行执行,那么共享数据是不是不必要保护的?

对不起,如果这是一个愚蠢的问题,但这是我一直想在多处理器/核心机器上的Python的东西。

同样的事情将适用于任何其他具有GIL的语言实现。

如果你在线程之间共享状态,你仍然需要锁。 GIL只在内部保护口译员。 您自己的代码中仍然可能有不一致的更新。

例如:

#!/usr/bin/env python import threading shared_balance = 0 class Deposit(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance balance = shared_balance balance += 100 shared_balance = balance class Withdraw(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance balance = shared_balance balance -= 100 shared_balance = balance threads = [Deposit(), Withdraw()] for thread in threads: thread.start() for thread in threads: thread.join() print shared_balance 

在这里,您的代码可以在读取共享状态( balance = shared_balance )和写回已更改的结果( shared_balance = balance )之间中断,导致更新丢失。 结果是共享状态的随机值。

为了使更新保持一致,运行方法需要lockingread-modify-write部分(循环内) 的共享状态,或者有一些方法来检测共享状态自读取以来何时发生更改 。

没有 – GIL只是保护python内部从多个线程改变他们的状态。 这是一个非常低级别的locking,足以使python自己的结构保持一致的状态。 它不包含您需要执行的应用程序级别locking,以覆盖您自己的代码中的线程安全性。

锁的本质是确保一个特定的代码只能由一个线程执行。 GIL对单个字节码大小的块进行强制执行,但通常希望locking跨越比这更大的代码块。

增加讨论:

因为GIL存在,有些操作在Python中是primefaces的,不需要locking。

http://www.python.org/doc/faq/library/#what-kinds-of-global-value-mutation-are-thread-safe

正如其他答案所述,只要应用程序逻辑需要它们(例如在生产者/消费者问题中),您仍然需要使用锁。

全局解释器locking防止线程同时访问解释器(因此CPython只使用一个核心)。 然而,据我所知,线程仍然被中断和预先安排,这意味着你仍然需要在共享数据结构上locking,以免你的线程踩在对方的脚趾上。

我一次又一次遇到的答案是Python中的multithreading很less值得花费,因为这个。 我听说过PyProcessing项目的好处 ,它使得多进程运行multithreading,像共享数据结构,队列等一样“简单”。(PyProcessing将作为多处理模块引入到即将到来的Python 2.6的标准库中)这让你围绕着GIL,因为每个过程都有自己的解释器。

这篇文章描述了GIL在一个相当高的层次:

特别感兴趣的是这些引言:

每十条指令(这个默认值可以改变),核心释放当前线程的GIL。 在这一点上,操作系统从所有线程中select一个线程来竞争锁(可能select刚刚释放GIL的同一个线程 – 你无法控制哪个线程被选中)。 该线程获取GIL,然后运行另外十个字节码。

请注意,GIL只限制纯Python代码。 可以编写扩展(通常用C编写的外部Python库)来释放锁,然后允许Python解释器与扩展分开运行,直到扩展重新获取锁。

听起来GIL只是提供了更less的可能的上下文切换实例,并且就多个python解释器实例而言,使得多核/处理器系统像单核一样运行,所以是的,您仍然需要使用同步机制。

这样想:

在单处理器计算机上,multithreading通过暂停一个线程并以另一个线程快速启动以使其看起来同时运行而发生。 这就像GIL的Python一样:只有一个线程实际运行。

问题是线程可以在任何地方挂起,例如,如果我想计算b =(a + b)* 3,这可能会产生如下的指令:

 1 a += b 2 a *= 3 3 b = a 

现在,让我们说运行在一个线程中,线程1或2后暂停,然后另一个线程开始运行:

 b = 5 

然后当另一个线程恢复时,b被旧的计算值覆盖,这可能不是预期的。

所以你可以看到,即使它们不是同时运行,你仍然需要locking。

您仍然需要使用锁(您的代码可能随时中断执行另一个线程,这可能会导致数据不一致)。 GIL的问题是,它阻止Python代码同时使用更多的核心(或多个处理器,如果可用的话)。

锁仍然需要。 我会试着解释为什么他们需要。

任何操作/指令都在解释器中执行。 GIL确保解释器在特定的时间由单个线程保持。 而你的multithreading程序在一个解释器中工作。 在任何特定的时刻,这个解释器都由一个单独的线程来保存。 这意味着只有持有解释器的线程正在运行

假设有两个线程(t1和t2),并且都想要执行两条读取全局variables值并递增的指令。

 #increment value global var read_var = var var = read_var + 1 

如上所述,GIL只确保两个线程不能同时执行一条指令,这意味着两个线程在任何特定的时刻都不能执行read_var = var 。 但是他们可以一个接一个地执行指令,你仍然有问题。 考虑这种情况:

  • 假设read_var是0。
  • GIL由线程t1保存。
  • t1执行read_var = var 。 所以,t1中的read_var为0. GIL只会确保这个读取操作在这个时刻不会被其他任何线程执行。
  • GIL给线程t2。
  • t2执行read_var = var 。 但是read_var仍然是0.所以,t2中的read_var是0。
  • GIL给予t1。
  • t1执行var = read_var+1 ,var变成1。
  • GIL给予t2。
  • t2认为read_var = 0,因为这就是它读的。
  • t2执行var = read_var+1 ,var变成1。
  • 我们的期望是var应该变成2。
  • 所以,必须使用一个锁来保持读取和递增的primefaces操作。
  • 哈里斯的答案将通过一个代码示例来解释它。

威尔·哈里斯的例子有一点点更新:

 class Withdraw(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance if shared_balance >= 100: balance = shared_balance balance -= 100 shared_balance = balance 

把一个值检查语句在撤回,我不看到负面了,更新似乎一致。 我的问题是:

如果GIL防止只有一个线程可以在任何primefaces时间执行,那么将在哪里陈旧的价值? 如果没有陈旧的价值,为什么我们需要locking? (假设我们只谈论纯粹的Python代码)

如果我理解正确,上述条件检查将不会在真正的线程环境中工作。 当多个线程同时执行时,可以创build陈旧的值,因此共享状态不一致,那么你真的需要一个锁。 但是如果python真的只允许一个线程在任何时候(时间分割线程),那么应该不可能存在过时的值,对吧?