盘点Python 里关于线程安全的那些事儿
1. 线程不安全是怎样的? 要搞清楚什么是线程安全,就要先了解线程不安全是什么样的。 比如下面这段代码,开启两个线程,对全局变量 number 各自增 10万次,每次增量 1。 from threading import Thread, Lock
number = 0
def target(): global number for _ in range(1000000): number += 1
thread_01 = Thread(targettarget=target) thread_02 = Thread(targettarget=target) thread_01.start() thread_02.start()
thread_01.join() thread_02.join()
print(number) 正常我们的预期输出结果,一个线程自增100万,两个线程就自增 200 万嘛,输出肯定为 2000000 。 可事实却并不是你想的那样,不管你运行多少次,每次输出的结果都会不一样,而这些输出结果都有一个特点是,都小于 200 万。 以下是执行三次的结果 1459782 1379891 1432921 这种现象就是线程不安全,究其根因,其实是我们的操作 number += 1 ,不是原子操作,才会导致的线程不安全。 2. 什么是原子操作? 原子操作(atomic operation),指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会切换到其他线程。 它有点类似数据库中的 事务。 在 Python 的官方文档上,列出了一些常见原子操作 L.append(x) L1.extend(L2) x = L[i] x = L.pop() L1[i:j] = L2 L.sort() x = y x.field = y D[x] = y D1.update(D2) D.keys() 而下面这些就不是原子操作 ii = i+1 L.append(L[-1]) L[i] = L[j] D[x] = D[x] + 1 像上面的我使用自增操作 number += 1,其实等价于 number = number + 1,可以看到这种可以拆分成多个步骤(先读取相加再赋值),并不属于原子操作。 这样就导致多个线程同时读取时,有可能读取到同一个 number 值,读取两次,却只加了一次,最终导致自增的次数小于预期。 当我们还是无法确定我们的代码是否具有原子性的时候,可以尝试通过 dis 模块里的 dis 函数来查看 (编辑:ASP站长网) |