python-lock-and-GIL

Python thread lock

为什么 python thread 有全局的 GIL 锁,python 多线程并发还需要枷锁?

首先,锁的目的是什么, 是保证事务的原子性! 这个事务的概念可以很大,也可以很小,大到一些列复杂操作组成的一个业务单元,小到执行一个计数加1操作。 因此, 原子性是根据业务需求来定的!原子性的范围决定了临界区的范围,也导致了锁开始和结束的位置我们称之为锁的粒度。

Python 没有真正的多线程,这句话一半对一半不对。Python不是没有多线程,Python 有多线程,只是多线程无法充分利用cpu并行(由于GIL的存在);那这个 GIL 既然是个锁,他锁的粒度有多大,我们说Python解释器在一个时刻只可能执行一个线程,这是正确的,因为GIL锁住的是线程的字节指令序列,其他线程要执行,必须拿到GIL才能执行,因此Python不存在真正意义上的多线程完全并行。实际上,永远是穿行的执行线程。那么这是不是意味着,多线程的对全局计数器的加1操作就不用枷锁呢?

import dis
num = 0
def f():
    global num
    num += 1

import threading
ts = []
for i in range(1000000):
    t = threading.Thread(target=f)
    ts.append(t)
    t.start()

for t in ts:
    t.join()

print num

这个的输出结果预期应该是 999999, 但结果却不是,我试验是999987. 我们dis出f的字节码看看;

  5           0 LOAD_GLOBAL              0 (num)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD         
              7 STORE_GLOBAL             0 (num)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
class Manager():
    count = 0
    def __init__(self):
        self.count += 1
    @classmethod
    def get_count(cls):
        return cls.count

manager = None
def get_default_manager():
    global manager
    if manager is None:
        manager = Manager()
    return manager

import dis
dis.dis(get_default_manager)

import threading
ts = []
for i in range(10000000):
    t = threading.Thread(target=get_default_manager)
    ts.append(t)
    t.start()

for t in ts:
    t.join()
12           0 LOAD_GLOBAL              0 (manager)
              3 LOAD_CONST               0 (None)
              6 COMPARE_OP               8 (is)
              9 POP_JUMP_IF_FALSE       24

 13          12 LOAD_GLOBAL              2 (Manager)
             15 CALL_FUNCTION            0
             18 STORE_GLOBAL             0 (manager)
             21 JUMP_FORWARD             0 (to 24)

 14     >>   24 LOAD_GLOBAL              0 (manager)
             27 RETURN_VALUE