Python (cpython) 关于内存屏障和原子性等的行为是否得到保证?
Is Python (cpython) behavior with respect to memory barriers and atomicity etc. guaranteed?
我想知道 Java 的“volatile”的等效项,并找到了这个答案。
这(基本上)说,由于 GIL,在 python 中,至少在 cpython 中,一切都是有效的。这是有道理的,一切都被 GIL 锁定,没有内存障碍需要担心,等等。但如果这被记录在案并由规范保证,而不是让它成为 c[=60= 的结果,我会更高兴] 恰好当前正在实施。
因为,假设我想要一个线程 post 数据而其他线程读取它,所以我可以选择这样的东西:
class XFaster:
def __init__(self):
self._x = 0
def set_x(self, x):
self._x = x
def get_x(self, x):
return self._x
class XSafer:
def __init__(self):
self._x = 0
self._lock = threading.Lock()
def set_x(self, x):
with self._lock:
self._x = x
def get_x(self, x):
with self._lock:
return self._x
我宁愿使用 XFaster
,甚至根本不使用 getter 和 setter。但我也想可靠地、“正确地”做事。是否有一些官方文件说这是可以的?比如说将值放入 dict
或附加到 list
怎么样?
换句话说,是否有一种系统的、有记录的方法来确定我可以在没有 threading.Lock
的情况下做什么(无需深入研究 dis
或类似的东西)?而且最好以一种不会与未来 python 版本中断的方式。
关于编辑:我感谢评论中的知情讨论。但我真正想要的是一些保证以下内容的规范:
如果我执行这样的操作:
# in the beginning
x.a == foo
# then two threads start
# thread 1:
x.a = bar
# thread 2
do_something_with(x.a)
我想确定:
- 当线程 2 读取
x.a
时,它读取 foo
或 bar
- 如果线程 2 中的读取发生在物理上晚于线程 1 中的赋值,那么它实际上读取
bar
以下是一些我不想发生的事情:
- 线程被安排在不同的处理器上,并且来自线程 1 的分配
x.a=bar
对线程 2 不可见
x.__dict__
正在重新散列,因此线程 2 读取垃圾
- 等等
TLDR:CPython 保证它自己的数据结构是线程安全的,不会损坏。这并不意味着 任何 自定义数据结构或代码都是无竞争的。
GIL 的目的是保护 CPython 的数据结构免受损坏。可以相信 internal 状态是线程安全的。
global interpreter lock (Python documentation – Glossary)
The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access. [...]
这也意味着跨线程更改的正确可见性。
然而,这并不意味着任何孤立的语句或表达式都是原子的:几乎任何语句或表达式都可以调用多个字节码指令。因此,GIL 明确地不为这些情况提供原子性。
具体来说,像x.a=bar
这样的语句可以通过object.__setattr__
or the descriptor protocol调用setter来执行任意多条字节码指令。它至少执行三个字节码指令,用于 bar
查找、x
查找和 a
赋值。
因此,Python保证visibility/consistency,但不保证不会出现竞争条件。如果对象同时发生变化,则必须对其进行同步以确保正确性。
我想知道 Java 的“volatile”的等效项,并找到了这个答案。
这(基本上)说,由于 GIL,在 python 中,至少在 cpython 中,一切都是有效的。这是有道理的,一切都被 GIL 锁定,没有内存障碍需要担心,等等。但如果这被记录在案并由规范保证,而不是让它成为 c[=60= 的结果,我会更高兴] 恰好当前正在实施。
因为,假设我想要一个线程 post 数据而其他线程读取它,所以我可以选择这样的东西:
class XFaster:
def __init__(self):
self._x = 0
def set_x(self, x):
self._x = x
def get_x(self, x):
return self._x
class XSafer:
def __init__(self):
self._x = 0
self._lock = threading.Lock()
def set_x(self, x):
with self._lock:
self._x = x
def get_x(self, x):
with self._lock:
return self._x
我宁愿使用 XFaster
,甚至根本不使用 getter 和 setter。但我也想可靠地、“正确地”做事。是否有一些官方文件说这是可以的?比如说将值放入 dict
或附加到 list
怎么样?
换句话说,是否有一种系统的、有记录的方法来确定我可以在没有 threading.Lock
的情况下做什么(无需深入研究 dis
或类似的东西)?而且最好以一种不会与未来 python 版本中断的方式。
关于编辑:我感谢评论中的知情讨论。但我真正想要的是一些保证以下内容的规范:
如果我执行这样的操作:
# in the beginning
x.a == foo
# then two threads start
# thread 1:
x.a = bar
# thread 2
do_something_with(x.a)
我想确定:
- 当线程 2 读取
x.a
时,它读取foo
或bar
- 如果线程 2 中的读取发生在物理上晚于线程 1 中的赋值,那么它实际上读取
bar
以下是一些我不想发生的事情:
- 线程被安排在不同的处理器上,并且来自线程 1 的分配
x.a=bar
对线程 2 不可见
x.__dict__
正在重新散列,因此线程 2 读取垃圾- 等等
TLDR:CPython 保证它自己的数据结构是线程安全的,不会损坏。这并不意味着 任何 自定义数据结构或代码都是无竞争的。
GIL 的目的是保护 CPython 的数据结构免受损坏。可以相信 internal 状态是线程安全的。
global interpreter lock (Python documentation – Glossary)
The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access. [...]
这也意味着跨线程更改的正确可见性。
然而,这并不意味着任何孤立的语句或表达式都是原子的:几乎任何语句或表达式都可以调用多个字节码指令。因此,GIL 明确地不为这些情况提供原子性。
具体来说,像x.a=bar
这样的语句可以通过object.__setattr__
or the descriptor protocol调用setter来执行任意多条字节码指令。它至少执行三个字节码指令,用于 bar
查找、x
查找和 a
赋值。
因此,Python保证visibility/consistency,但不保证不会出现竞争条件。如果对象同时发生变化,则必须对其进行同步以确保正确性。