[全]线程安全_第1页
[全]线程安全_第2页
[全]线程安全_第3页
[全]线程安全_第4页
[全]线程安全_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、线程安全1. 线程不安全是怎样的?要搞清楚什么是线程安全,就要先了解线程不安全是什么样的。比如下面这段代码,开启两个线程,对全局变量 number 各自增 10万次,每次自增 1。from threading import Thread, Locknumber = 0def target(): global number for _ in range(1000000): number += 1thread_01 = Thread(target=target)thread_02 = Thread(target=target)thread_01.start()thread_02.start()th

2、read_01.join()thread_02.join()print(number)正常我们的预期输出结果,一个线程自增100万,两个线程就自增 200 万嘛,输出肯定为 2000000 。可事实却并不是你想的那样,不管你运行多少次,每次输出的结果都会不一样,而这些输出结果都有一个特点是,都小于 200 万。以下是执行三次的结果145978213798911432921这种现象就是线程不安全,究其根因,其实是我们的操作number += 1,不是原子操作,才会导致的线程不安全。2. 什么是原子操作?原子操作(atomic operation),指不会被线程调度机制打断的操作,这种操作一旦开始

3、,就一直运行到结束,中间不会切换到其他线程。它有点类似数据库中的事务。在 Python 的官方文档上,列出了一些常见原子操作L.append(x)L1.extend(L2)x = Lix = L.pop()L1i:j = L2L.sort()x = yx.field = yDx = yD1.update(D2)D.keys()而下面这些就不是原子操作i = i+1L.append(L-1)Li = LjDx = Dx + 1像上面的我使用自增操作number += 1,其实等价于number = number + 1,可以看到这种可以拆分成多个步骤(先读取相加再赋值),并不属于原子操作。这样就

4、导致多个线程同时读取时,有可能读取到同一个 number 值,读取两次,却只加了一次,最终导致自增的次数小于预期。当我们还是无法确定我们的代码是否具有原子性的时候,可以尝试通过dis模块里的 dis 函数来查看当我们执行这段代码时,可以看到number += 1这一行代码,由两条字节码实现。 BINARY_ADD:将两个值相加 STORE_GLOBAL: 将相加后的值重新赋值每一条字节码指令都是一个整体,无法分割,他实现的效果也就是我们所说的原子操作。当一行代码被分成多条字节码指令的时候,就代表在线程线程切换时,有可能只执行了一条字节码指令,此时若这行代码里有被多个线程共享的变量或资源时,并且

5、拆分的多条指令里有对于这个共享变量的写操作,就会发生数据的冲突,导致数据的不准确。为了对比,我们从上面列表的原子操作拿一个出来也来试试,是不是真如官网所说的原子操作。这里我拿字典的 update 操作举例,代码和执行过程如下图从截图里可以看到,info.update(new)虽然也分为好几个操作 LOAD_GLOBAL:加载全局变量 LOAD_ATTR: 加载属性,获取 update 方法 LOAD_FAST:加载 new 变量 CALL_FUNCTION:调用函数 POP_TOP:执行更新操作但我们要知道真正会引导数据冲突的,其实不是读操作,而是写操作。上面这么多字节码指令,写操作都只有一个

6、(POP_TOP),因此字典的 update 方法是原子操作。3. 实现人工原子操作在多线程下,我们并不能保证我们的代码都具有原子性,因此如何让我们的代码变得具有 “原子性” ,就是一件很重要的事。方法也很简单,就是当你在访问一个多线程间共享的资源时,加锁可以实现类似原子操作的效果,一个代码要嘛不执行,执行了的话就要执行完毕,才能接受线程的调度。因此,我们使用加锁的方法,对例子一进行一些修改,使其具备原子性。from threading import Thread, Locknumber = 0lock = Lock()def target(): global number for _ in

7、range(1000000): with lock: number += 1thread_01 = Thread(target=target)thread_02 = Thread(target=target)thread_01.start()thread_02.start()thread_01.join()thread_02.join()print(number)此时,不管你执行多少遍,输出都是 2000000.4. 为什么 Queue 是线程安全的?Python 的 threading 模块里的消息通信机制主要有如下三种:1. Event2. Condition3. Queue使用最多的是 Queue,而我们都知道它是线程安全的。当我们对它进行写入和提取的操作不会被中断而导致错误,这也是我们在使用队列时,不需要额外加锁的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论