线程不安全一例

2020年6月5日 / 40次阅读 / Last Modified 2020年6月5日
多线程

python多线程虽然有GIL管着,在IO密集型任务的时候,还是可以用的。本文给出一个很简单的线程不安全的示例,说明一个事情:凡是共享的资源,都要自己加锁控制,不要以为python的每一行脚本都是原子操作,虽然python自定的确定义了一些原子操作,但是不要去依赖,如果哪一天python解释器修改了内部机制,你的代码就会崩溃;对于共享资源,无论有几个修改线程,加把锁更安全。

线程不安全一例:

from threading import Thread, Lock

number = 0

def target():
    global number
    for _ in range(1000000):
        number += 1

thread_01 = Thread(target=target)
thread_02 = Thread(target=target)
thread_01.start()
thread_02.start()

thread_01.join()
thread_02.join()

print(number)

两个线程,并发对一个全局变量进行+1操作,我们预期的结果是2000000,但是不是,每一次运行都不是,而且还不一样:

D:\py>python thread_not_safe.py
1391219

D:\py>python thread_not_safe.py
1241460

D:\py>python thread_not_safe.py
1210578

D:\py>python thread_not_safe.py
1221867

D:\py>python thread_not_safe.py
1136912

正确的代码应该是这样的:

from threading import Thread, Lock

number = 0
mutex = Lock()

def target():
    global number
    for _ in range(1000000):
        mutex.acquire()
        number += 1
        mutex.release()

thread_01 = Thread(target=target)
thread_02 = Thread(target=target)
thread_01.start()
thread_02.start()

thread_01.join()
thread_02.join()

print(number)

这样每次运行代码得到的结果都是2000000!

-- EOF --

本文链接:https://www.pynote.net/archives/2047

留言区

《线程不安全一例》有5条留言

电子邮件地址不会被公开。 必填项已用*标注

  • 麦新杰

    关于+1操作的bytecode:

    >>> import dis
    >>> a = 1
    >>> dis.dis('a += 1')
      1           0 LOAD_NAME                0 (a)
                  2 LOAD_CONST               0 (1)
                  4 INPLACE_ADD
                  6 STORE_NAME               0 (a)
                  8 LOAD_CONST               1 (None)
                 10 RETURN_VALUE
     [回复]

  • 麦新杰

    真正引起数据冲突的,不是读,而是写! [回复]

  • 麦新杰

    原因说明:多个线程同时读取时,有可能读取到同一个 number 值,读取两次,却只加了一次,最终导致自增的次数小于预期。 [回复]

    • 麦新杰

      因此,其实有一种情况是不需要mutex的:只有一个线程在更新,而其它线程只是读取。 [回复]

  • 麦新杰

    When in doubt, use a mutex! [回复]


前一篇:
后一篇:

More


©Copyright 麦新杰 Since 2019 Python笔记

go to top