精华内容
下载资源
问答
  • Python原子性操作是如何实现的
    千次阅读
    2020-12-23 11:32:41

    GIL保证字节码级别的原子性和线程安全性,因此当个字节码执行一定是安全的,执行结果一定是一致的。

    而有些操作,底层需要通过多个字节码来完成,这样的操作就不是原子的,因此不是线程安全的。举个例子,a+=1 。反编译这个语句,发现它由4个字节码组成:>>> dis.dis(compile('a+=1', '', 'exec'))

    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

    这个简单的语句,背后需要 4 个字节码协作完成:LOAD_NAME 将 a 当前的值加载进运行栈;

    LOAD_CONST 将常量 1 加载到运行栈;

    INPLACE_ADD 对栈上两个操作数进行加法运算;

    STORE_NAME 将计算结果保存;

    如果你学过汇编的话,你会发现Python字节码跟汇编指令非常像!GIL保证当个字节码的执行不会受到其他线程的任何干扰,但是任何字节码间都可能发生线程切换。

    假设两个线程同时自增变量a,a当前值为0;线程A执行到第3步,自增结果1已算出,但未保存;这时线程B得到调度开始执行,同样算出结果1并抢先保存了;A回过头来将结果1保存,B的结果被覆盖了,最终a的值是1。然而,两个线程对a自增,它的值讲道理应该是2!这就是并发操作产生的竞争态,解决方法是用一个锁将这几个字节码作为原子操作保护起来。

    我写过一个关于Python虚拟机内部实现的专栏Python源码深度剖析,里面有些许介绍,有兴趣可以看看~

    更多相关内容
  • 主要介绍了Python变量及数据类型用法原理汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 谷歌的风格指南建议不要这样做我并不是说Google样式指南是终极真理,但是rationale in the "Threading" section提供了一些见解(highlight是我的):Do not rely on the atomicity of built-in types.While Python’s ...

    谷歌的风格指南建议不要这样做

    我并不是说Google样式指南是终极真理,但是rationale in the "Threading" section提供了一些见解(highlight是我的):Do not rely on the atomicity of built-in types.

    While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if __hash__ or __eq__ are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries).

    Use the Queue module's Queue data type as the preferred way to communicate data between threads. Otherwise, use the threading module and its locking primitives. Learn about the proper use of condition variables so you can use threading.Condition instead of using lower-level locks.

    所以我的解释是,在Python中,一切都像dict一样,当你在后端做a = b时,globals['a'] = b就会发生,这很糟糕,因为dict不一定是线程安全的。

    但是对于单个变量,Queue并不理想,因为我们希望它只包含一个元素,而且我在stdlib中找不到一个完美的预先存在的容器来自动同步一个.set()方法。所以现在我只想:import threading

    myvar = 0

    myvar_lock = threading.Lock()

    with myvar_lock:

    myvar = 1

    with myvar_lock:

    myvar = 2This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access.

    展开全文
  • 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() 而下面这些就不是原子操作 i = i+1 ...

    在并发编程时,如果多个线程访问同一资源,我们需要保证访问的时候不会产生冲突,数据修改不会发生错误,这就是我们常说的 线程安全 。

    那什么情况下,访问数据时是安全的?什么情况下,访问数据是不安全的?如何知道你的代码是否线程安全?要如何访问数据才能保证数据的安全?

    本篇文章会一一回答你的问题。

    1. 线程不安全是怎样的?

    要搞清楚什么是线程安全,就要先了解线程不安全是什么样的。

    比如下面这段代码,开启两个线程,对全局变量 number 各自增 10万次,每次自增 1。

    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)

    正常我们的预期输出结果,一个线程自增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()

    而下面这些就不是原子操作

    i = i+1

    L.append(L[-1])

    L[i] = L[j]

    D[x] = D[x] + 1

    像上面的我使用自增操作 number += 1,其实等价于 number = number + 1,可以看到这种可以拆分成多个步骤(先读取相加再赋值),并不属于原子操作。

    这样就导致多个线程同时读取时,有可能读取到同一个 number 值,读取两次,却只加了一次,最终导致自增的次数小于预期。

    当我们还是无法确定我们的代码是否具有原子性的时候,可以尝试通过 dis 模块里的 dis 函数来查看

    当我们执行这段代码时,可以看到 number += 1 这一行代码,由两条字节码实现。

    BINARY_ADD :将两个值相加

    STORE_GLOBAL: 将相加后的值重新赋值

    每一条字节码指令都是一个整体,无法分割,他实现的效果也就是我们所说的原子操作。

    当一行代码被分成多条字节码指令的时候,就代表在线程线程切换时,有可能只执行了一条字节码指令,此时若这行代码里有被多个线程共享的变量或资源时,并且拆分的多条指令里有对于这个共享变量的写操作,就会发生数据的冲突,导致数据的不准确。

    为了对比,我们从上面列表的原子操作拿一个出来也来试试,是不是真如官网所说的原子操作。

    这里我拿字典的 update 操作举例,代码和执行过程如下图

    从截图里可以看到,info.update(new) 虽然也分为好几个操作

    LOAD_GLOBAL:加载全局变量

    LOAD_ATTR: 加载属性,获取 update 方法

    LOAD_FAST:加载 new 变量

    CALL_FUNCTION:调用函数

    POP_TOP:执行更新操作

    但我们要知道真正会引导数据冲突的,其实不是读操作,而是写操作。

    上面这么多字节码指令,写操作都只有一个(POP_TOP),因此字典的 update 方法是原子操作。

    3. 实现人工原子操作

    在多线程下,我们并不能保证我们的代码都具有原子性,因此如何让我们的代码变得具有 “原子性” ,就是一件很重要的事。

    方法也很简单,就是当你在访问一个多线程间共享的资源时,加锁可以实现类似原子操作的效果,一个代码要嘛不执行,执行了的话就要执行完毕,才能接受线程的调度。

    因此,我们使用加锁的方法,对例子一进行一些修改,使其具备原子性。

    from threading import Thread, Lock

    number = 0

    lock = Lock()

    def target():

    global number

    for _ in range(1000000):

    with lock:

    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)

    此时,不管你执行多少遍,输出都是 2000000.

    4. 为什么 Queue 是线程安全的?

    Python 的 threading 模块里的消息通信机制主要有如下三种:

    Event

    Condition

    Queue

    使用最多的是 Queue,而我们都知道它是线程安全的。当我们对它进行写入和提取的操作不会被中断而导致错误,这也是我们在使用队列时,不需要额外加锁的原因。

    他是如何做到的呢?

    其根本原因就是 Queue 实现了锁原语,因此他能像第三节那样实现人工原子操作。

    原语指由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性;即原语的执行必须是连续的,在执行过程中不允许被中断。

    展开全文
  • python原子操作

    2020-12-08 08:42:52
    python编译器会将代码编译为bytecode,对于python而言,每一行bytecode都是原子操作。A global interpreter lock (GIL) is used internally to ensure that only one thread runs in the Python VM at a time. In ...

    python编译器会将代码编译为bytecode,对于python而言,每一行bytecode都是原子操作。

    A global interpreter lock (GIL) is used internally to ensure that only one thread runs in the Python VM at a time. In general, Python offers to switch among threads only between bytecode instructions; how frequently it switches can be set via sys.setswitchinterval(). Each bytecode instruction and therefore all the C implementation code reached from each instruction is therefore atomic from the point of view of a Python program.

    In theory, this means an exact accounting requires an exact understanding of the PVM bytecode implementation. In practice, it means that operations on shared variables of built-in data types (ints, lists, dicts, etc) that “look atomic” really are.

    下面这些操作是原子的:

    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()

    下面这些不是原子的:

    i = i+1

    L.append(L[-1])

    L[i] = L[j]

    D[x] = D[x] + 1

    关于 i = i+1不是原子操作,请看线程不安全一例。

    关于这部分内容的官方链接:https://docs.python.org/3.5/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe

    When in dount, use mutex!

    -- EOF --

    展开全文
  • Python变量声明

    千次阅读 2019-07-09 16:39:31
    Python 与大多数其它语言一样有局部变量和全局变量之分,但是它没有明显的变量声明。变量通过首次赋值产生,当超出作用范围时自动消亡。 Example 1. 定义 myParams 变量 首先注意缩进。 if 语句是代码块,需要...
  • A global interpreter lock (GIL) is used internally to ensure that only one thread runs in the Python VM at a time. In general, Python offers to switch among threads only between bytecode instructions;...
  • 原子变量AtomicInteger

    2015-10-23 16:14:31
    AtomicInteger适用范围:类的成员变量,在多线程中,数据被共享的,多个线程操作这个变量,才使用。局部变量及一个方法中不需要
  • J2SE 5.0提供了一组atomic class来帮助我们简化同步处理。基本工作原理是使用了CAS实现了对一个...在需要访问两个或两个以上 atomic变量的程序代码(或者是对单一的atomic变量执行两个或两个以上的操作)通常都需要被s
  • 这篇文章主要介绍了Python中的四种队列,需要的朋友可以参考下 队列是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表。 在Python文档中搜索队列(queue)会发现,Python标准库中包含了四种队列,...
  • python变量

    2020-11-25 22:21:16
    在使用python之前一直是使用java的,对于java类中的变量概念比较牢固,所以在迁移到python中只有一直有一点云山雾绕的,在某段时间一直不敢在类中定义变量。一直到昨天深入的理解了一下python变量的定义,终于有...
  • 首发于微信民众号:Python编程时光在并发编程时,若是多个线程接见统一资源,我们需要保证接见的时刻不会发生冲突,数据修改不会发生错误,这就是我们常说的 线程平安 。那什么情况下,接见数据时是平安的?什么情况...
  • python变量的命名规范

    千次阅读 2020-12-05 13:17:02
    模块名:小写字母,单词之间用_分割ad_stats.py包名:和模块名一样类名:单词首字母大写AdStatsConfigUtil全局变量名(类变量,在java中相当于static变量):大写字母,单词之间用_分割NUMBERCOLOR_WRITE普通变量:...
  • 一、共享变量共享变量:当多个线程访问同一个变量的时候。会产生共享变量的问题。例子:import threadingsum = 0loopSum = 1000000def myAdd():global sum, loopSumfor i in range(1, loopSum):sum += 1def myMinu()...
  • (注意Python变量没有属性__name__,至少在Python中没有属性2.7.x)换句话说,如果我有一个变量,比如:12foo = dict()foo["bar"] = 2我正在寻找一个函数/属性,例如retrieve_name:1retriev...
  • 多线程下变量原子操作的几种方法

    千次阅读 2018-07-04 19:09:01
     当然我们知道,count++这种操作不是原子的。一个自加操作,本质是分成三步的: 1 从缓存取到寄存器 2 在寄存器加1 3 存入缓存。 由于时序的因素,多个线程操作同一个全局变量,会出现问题。这也是并发编程的...
  • threading由于全局锁GIL的存在,python的多线程一直名不副实,换句话说,python并不能真正地并行执行threading模块。在同一个CPU上开启threading的多线程,实际上同一时刻时刻只能执行一个线程。而不同的线程实际上...
  • python变量作用域共四种:局部作用域local、嵌套作用域enclosing function locals、全局作用域global、内置作用域builtin 先看变量名的产生:赋值语句=,import、def,函数内部还有形参。 局部作用域: 一般...
  • 原子变量自增的实现

    千次阅读 2018-01-06 20:28:57
    原子变量自增大致有两种方式;一种是基于CPU硬件支持,而另一种为软件层面自旋实现;两者各有各的优势;理论上说软件 层面的现实是最通用的,不过它也带来了一些棘手的问题比如每个线程都在高频率的执行原子自增...
  • 作者简介:笔名seaboat,擅长工程算法、人工智能算法、自然语言处理、计算机视觉、架构、分布式、高并发、大数据和搜索引擎等方面的技术,大多数编程语言都会使用,但更擅长Java、Python和C++。平时喜欢看书写作、...
  • 这种特性就叫原子python中使用[互斥锁Mutex]防止多个线程同时对一个变量修改导致的错误. mutex = threading.Lock() 创建一把锁,默认没上锁 mutex.acquire()上锁 mutex.release()解锁 如果一个线程执行到mutex....
  • 谈谈原子变量与锁的性能比较

    千次阅读 2019-01-10 20:39:00
    2019独角兽企业重金招聘Python工程师标准>>> ...
  • 一、python基础之数据类型与变量

    千次阅读 2021-01-29 19:49:13
    一、变量声明变量#!/usr/bin/env pythonage=9name='...数据类型程序的本质就是驱使计算机去处理各种状态的变化,这些状态分为很多种2.1 什么是数据类型及数据类型分类python中的数据类型python使用对象模型来存储数...
  • 中国大学MOOC: 以下不是Python中合法变量名的是______。答:5MyGod“32位微型计算机”中的32指的是( )答:机器字长以下是细胞因子治疗肿瘤的特点的是: 局部优于全身/#/疗效快而持久/#/短期低剂量效佳/#/联合差于单一...
  • 3.Python对象

    2020-12-23 11:32:33
    对象Python 对象 : Python使用对象模型来存储数据.构造任何类型的值都是一个对象,. 尽管Python通常当成一种 "面向对象的编程语言" ,但是你完全能够写出不使用任何类和实例的实用脚本.所有的Python对象都拥有三个特征...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,668
精华内容 7,067
关键字:

python原子变量