Python 多线程的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
chaleaoch
V2EX    Python

Python 多线程的问题

  •  
  •   chaleaoch 2020 年 5 月 22 日 3923 次点击
    这是一个创建于 2060 天前的主题,其中的信息可能已经有所发展或是发生改变。
    total = 0 def add(): #1. dosomething1 #2. io 操作 # 1. dosomething3 global total for i in range(1000000): total += 1 def desc(): global total for i in range(1000000): total -= 1 import threading thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total) # 178412 

    为啥不是 0. 已经做好被喷准备.

    19 条回复    2020-05-27 11:24:21 +08:00
    simapple
        1
    simapple  
       2020 年 5 月 22 日
    加锁
    chanchancl
        2
    chanchancl  
       2020 年 5 月 22 日   1
    简单来说 total 不是线程安全的,
    这个不只在 Python 中会出现,而是任何多线程语言下都会出现的现象,
    解决方案就是访问前加锁,
    或者用原子操作

    不过我感觉你是来钓鱼的
    chaleaoch
        3
    chaleaoch  
    OP
       2020 年 5 月 22 日
    @chanchancl 不是钓鱼 python 不是有 GIL 吗?
    reself
        4
    reself  
       2020 年 5 月 22 日   1
    @chaleaoch GIL:执行是单线程的,不代表线程的上下文是受保护的。即单线程的执行方式!=线程安全。
    sujin190
        5
    sujin190  
       2020 年 5 月 22 日   2
    你可以 dis 看下生成的字节指令就知道了,+= 1 也是需要好多条指令的,python 的 GIL 应该只是在单条字节指令保证原子性吧,但是你一行代码很多时候都是多条字节指令的吧
    chenxytw
        6
    chenxytw  
       2020 年 5 月 22 日 via iPhone   1
    += 和 -= 不是原子的
    xiaolinjia
        7
    xiaolinjia  
       2020 年 5 月 22 日   1
    1. GIL 意思是任何时候只有一个线程运行
    2. 因为增量赋值不是原子操作,具体可看 dis.dis('a += 1') 的字节码不止一步
    3. 线程是系统调度的,你不知道何时切换
    4. 好,那你怎么就知道在做增量赋值的字节码某一步的时候,不会切换到了另一个线程呢?
    lithbitren
        8
    lithbitren  
       2020 年 5 月 22 日   1
    全局定义一个 lock = threading.Lock(),+=、-=之前增加一个 with lock:的块
    jugelizi
        9
    jugelizi  
       2020 年 5 月 22 日 via iPhone
    典型程序员思维。
    难道一百个男生 一百个女生。
    就一定是每个男生都有对象吗
    vagrantear
        10
    vagrantear  
       2020 年 5 月 22 日
    @jugelizi 程序员确实想每个男生都有对象(逃
    dahuahua
        11
    dahuahua  
       2020 年 5 月 22 日
    GIL 锁也不一定安全,任何指令运行都是有一个周期的,到时间还是乖乖把锁交出去了。
    CzaOrz
        12
    CzaOrz  
       2020 年 5 月 23 日
    我猜你是这样想的...

    无论先后,只要有 1000000 的+或者-执行,那么最后的结果肯定为 0,
    比如先执行 999999 次+,执行 1 次-,再跑 1 次+,再跑 999999 次-,
    无论过程如何,结果都应该为 0 才对

    实际 pythonGIL 全局解释器,若无其他因素,应该是每个线程执行 100 字节,就会释放锁,执行其他线程
    两个线程共同操作同一个全局变量,就会导致结果不可预测,
    你已经没有办法模拟出内部的具体执行流程了,就像你的数据结果 178412 没有任何意义
    多跑几次也会是不一样的结果

    像楼上大佬们说的安全、加锁之类的,就是为了保证每次能够完整的执行一次+或者-
    yuruizhe
        13
    yuruizhe  
       2020 年 5 月 23 日
    个人猜测
    1.初始 count=0
    2.线程 add 取 count=0
    3.线程 desc 取 count=0
    4.线程 add 计算 tmp=count+1=0
    5.线程 desc 计算 tmp=count-1=-1
    6.线程 desc 写入 count=-1
    7 线程 add 写入 count=1
    ...
    由于调度,某些写入操作的数值被覆盖掉了,没被下一次计算正确读取
    wuwukai007
        14
    wuwukai007  
       2020 年 5 月 23 日
    += 不是线程安全的
    black11black
        15
    black11black  
       2020 年 5 月 23 日
    你可以 global list,然后在每个线程里 for ... list.append(1),最后 sum(list)大概就能得到你想要的结果了。狗头
    chanchancl
        16
    chanchancl  
       2020 年 5 月 25 日
    @chaleaoch
    GIL 保证同时只有一个线程运行,但是并不保证多线程之间的执行顺序。
    比如
    Thread1 Thread2
    Lock GIL
    read total,0
    add total,1
    此时进入休眠
    Unlock GIL
    Lock GIL
    read total, 0
    add total, 1
    write total, 1
    休眠
    Unlock GIL
    Lock GIL
    write total
    此时 total 的值还是 1
    Unlock GIL

    总的来说,GIL 保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制。而对于程序中自己定义的数据则没有任何的保护效果,所以当程序中出现了共享自定义的数据时就要自己加锁。
    这句话来自 : https://www.cnblogs.com/liuxiaolu/p/10215629.html
    chanchancl
        17
    chanchancl  
       2020 年 5 月 25 日
    完了,v2 把我的空格吞了,
    总之上面的回复,

    Lock GIL
    read total,0
    add total,1
    此时进入休眠
    Unlock GIL
    这一部分,由线程一执行

    Lock GIL
    read total, 0
    add total, 1
    write total, 1
    休眠
    Unlock GIL
    这一部分,由线程二执行

    Lock GIL
    write total
    此时 total 的值还是 1
    Unlock GIL
    这一部分,由线程一执行

    其实由线程几执行无所谓,重要的是这里有一个切换进程的动作。
    Python 的解释器你可以理解为一个可以执行指令的 CPU

    而赋值这些操作都不是原子的,不应该依赖 GIL 去做任何事,

    GIL 本身也不是 Python 的特性,而是 CPython 这个实现的特性
    Fasion
        18
    Fasion  
       2020 年 5 月 27 日
    total += 1 不是一个原子操作,在 Python 虚拟机内部,由多条字节码构成,字节码是原子的:

    >>> dis.dis(compile('total += 1', '', 'exec'))
    1 0 LOAD_NAME 0 (total)
    2 LOAD_CONST 0 (1)
    4 INPLACE_ADD
    6 STORE_NAME 0 (total)
    8 LOAD_CONST 1 (None)
    10 RETURN_VALUE

    线程在任意字节码间都可能发生切换,因此多线程下可能会发生数值相互覆盖的问题。

    对 Python 虚拟机实现该兴趣的童鞋可以关注我写的专栏: https://www.imooc.com/read/76,里面有详细介绍。
    Fasion
        19
    Fasion  
       2020 年 5 月 27 日
    专栏地址写得有点问题,更新一下: https://www.imooc.com/read/76
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     995 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 18:34 PVG 02:34 LAX 10:34 JFK 13:34
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86