【问题标题】:Python atomic access (related to threads)Python 原子访问(与线程相关)
【发布时间】:2016-10-27 10:43:37
【问题描述】:

当你开始使用线程时,你必须非常了解一些东西,主要是关于共享数据的同步。你必须使用锁、rlocks、信号量等等。即使是非常简单的共享数据也能写吗?

例如,我有两个线程共享一个全局资源,一个简单的布尔变量。

import threading
import random
import time

active = True

class T1(threading.Thread):
    def run(self):
        while active:
            print("Hello")      # do some work
            time.sleep(0.1)

class T2(threading.Thread):
    def run(self):
        global active
        while random.random() > 0.05:
            time.sleep(1)
        active = False

t1, t2 = T1(), T2()
t1.start()
t2.start()
t1.join()
t2.join()

它似乎有效,但 python 能保证它有效吗?如果是,那么它的工作原理是什么?

我有三个答案。

  1. 之所以有效,是因为布尔变量的访问始终是原子的(它不能被另一个线程中断)。在这种情况下,Python 保证什么是原子操作?整数、列表、字典的访问是原子的吗?
  2. Python 不保证此代码每次都能正常工作。解决方案是使用锁来访问两个线程中的活动变量。
  3. 之所以有效,是因为只有一个线程写入变量。另一个线程只读取它。

这是另一个代码:

import threading
import random
import time

numbers = [3]

class T1(threading.Thread):
    def run(self):
        while sum(numbers) % 3 == 0:
            print("Hello")      # do some work
            time.sleep(0.1)

class T2(threading.Thread):
    def run(self):
        global active
        while random.random() > 0.2:
            numbers.extend([1, 2])
            time.sleep(1)
        numbers.append(4)

t1, t2 = T1(), T2()
t1.start()
t2.start()
t1.join()
t2.join()

现在单一和“复杂”指令numbers.extend([1, 2])可以被T1线程中断吗?如果是这种情况,在添加 1 之后,添加 2 之前,T1 可能会在 T2 之前意外停止。

【问题讨论】:

    标签: python multithreading thread-safety


    【解决方案1】:

    简短回答:它总是有效(至少在 CPython 中)

    它的工作原理是 GIL(全局解释器锁)。 https://wiki.python.org/moin/GlobalInterpreterLock

    这保证没有两个线程同时执行 Python 代码,因此永远不会同时进行读取/写入。

    如果您对特定的非 CPython 实现有疑问,那可能值得自己提出问题。

    我认为其他 Python 实现(除了 CPython)确实试图保留使用 CPython 的程序员已经开始依赖的某些行为,但我不确定每个项目如何/在多大程度上这样做。

    【讨论】:

    • 请注意,来自两个线程的行可能是任意交错的,但您的代码并不真正依赖于任何一系列原子操作(这需要您更加小心)。您的代码仅依赖于原子执行的单行,这在 CPython 中应该始终是可靠的
    • “同时编码”和“在单个变量上”是什么意思?在 Python 中,一条 single 指令可以在变量和复杂对象上做很多事情。 Python 是否保证所有单个指令(例如两个对象或循环或函数调用之间的复制......)不能被另一个线程中断?我如何理解一个线程何时可以中断另一个线程?
    • 我认为,但不知道一个线程可以在任意两条指令之间中断另一个线程。表达式i = 2 + 3 * 10 + 17 有许多单独的指令,但是会计算整个右侧,最后会发生赋值i = 49。如果i 的值以前是10,则重复检查i 值的另一个线程只会看到i == 10i == 49。在 2 + 3 * 10 + 17 的计算过程中,i 永远不会介于两者之间
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多