Python多线程掉坑简记

废话不多说,先来看下面,代码,预测一下运行结果:

代码1-1

from concurrent import futures


def _add(loop=1):
    global number
    for _ in range(loop):
        number += 1


def _sub(loop=1):
    global number
    for _ in range(loop):
        number -= 1


if __name__ == "__main__":
    loop_time = 1E7

    number = 0
    with futures.ThreadPoolExecutor(max_workers=2)as pool:
        future1 = pool.submit(_add, loop_time)
        future2 = pool.submit(_sub, loop_time)
    print(number)

答案揭晓,无论你是用哪个版本的Python, 其结果都为0

如果你感到疑惑,不妨将第_add或_sub函数中的循环次数改动一下,使得两个函数的循环次数不对等,如下所示:

代码1-2

from concurrent import futures


def _add(loop=1):
    global number
    for _ in range(loop):
        number += 1


def _sub(loop=1):
    global number
    for _ in range(loop-1000):
        number -= 1


if __name__ == "__main__":
    loop_time = 1E7

    number = 0
    with futures.ThreadPoolExecutor(max_workers=2)as pool:
        future1 = pool.submit(_add, loop_time)
        future2 = pool.submit(_sub, loop_time)
    print(number)

运行结果依然是0!!

有伙伴就要吃惊了,如果在Python中, builtin的写入数据是线程安全的,上述结果理应是1000才对啊!   后来经过我的排查发现,实际问题出在第17行。 让我来考考你:

loop_time = 1E7
print(type(loop_time))

 loop_time是什么数据类型呢?

。。。。。。。。。。。。。。。。。。。。

答案是: <class 'float'>

这也就意味着当你运行下述代码:

for _ in range(1E7):
    pass

   你会发现,报错!

那么,为什么在开启的子线程中,这样的报错却没有显示出来呢?

这正是线程执行的第一大坑,线程报错不会显示出来,而是提前终止了线程! 这也就意味着,在线程中的for循环,压根没有执行!!!

好的,让我们给17行代码加上:

loop_time = int(1E7)

现在再运行最开始的代码1-1, 你猜猜其结果是什么?

。。。。。。。。。。。。。。。。。。。。。

答案是,与Python解释器有关!!!

如果你是在Python3.8后的版本运行1-1修正后的代码,你会发现无论你怎么运行,number的值都为0!!

反之,如果你是在Python3.8之前的版本运行上述代码,number的值是不确定的,它甚至可以是负数!

Problem1: 为什么number的结果不定呢?

ans: 这是由于在Python3.8之前的版本中,很多builtin的写入操作并不是线程安全的;一个简单的代码:

a += 1

它事实上是包含至少两步的。第一步就是对数据进行读取,确定了这个数据的值后,再对这个值进行++; 下面是一个简化理解的代码:

# a += 1类似于:
temp = a
temp = temp + 1
a = temp

这也就意味着,在多线程环境中,为简化问题假设只有两个线程A, B

假设a的初始值为0, A线程和B线程都希望给a+=1;

①但A线程执行到a += 1时, 实际对应执行到Cpython中读取数据那一步,此时读取到a = 0,准备+1;

②与此同时,B线程已经快人一步,在读取完a = 0后,执行完了a += 1的操作;

③随后切换到A线程执行 a = 0 + 1的操作。

结果可想而知,a的值不再是预期的2, 而是1  !!!

这便是多线程中最臭名昭著的竞态问题!!

problem2: 为什么python3.8后这个builtin同时写入的问题就不存在了呢?

有了problem1的铺垫,这就极好理解了。Python3.8后为builtin的写入操作加锁了,往后的写入操作都变成了原子性操作,不会出现读一半还没写入就被别人抢先一步写入的情况;

我们简单看看kimi的解释:

"""
实际上,从Python 3.8开始,CPython(Python的官方实现)引入了对多线程更好的支持,特别是在原子操作方面。这意味着在Python 3.8及更高版本中,一些操作(如对简单全局变量的+=操作)在多线程环境中是原子的,因此在这些版本中,你的代码会输出预期的0。

在内部,这种原子性可能是通过锁或其他同步机制实现的,但这是CPython的内部实现细节,对于Python程序员来说是透明的。
"""

总结??? 懒得总结。评论区留下你的疑问与见解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值