为什么自定义类型初始值设定项设置的属性需要受到保护?

问题描述 投票:0回答:1

CPython 教程为自定义类型定义了一个自定义初始值设定项,其中包含以下几行:

if (first) {
    tmp = self->first;
    Py_INCREF(first);
    self->first = first;
    Py_XDECREF(tmp);
}

但是教程建议不要使用这个更简单但更邪恶的版本:

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}

原因如下:

但这会有风险。我们的类型不限制第一个成员的类型,因此它可以是任何类型的对象。它可能有一个析构函数,导致执行尝试访问第一个成员的代码;或者该析构函数可以释放全局解释器锁并让任意代码在访问和修改我们的对象的其他线程中运行。

我明白了多线程编程能够打破简单版本的原因。可能存在竞争条件,其中 new

self->first
由一个线程设置并由另一个线程释放(通过
Py_XDECREF
)。在正确的版本中可以避免这种情况,因为两个线程都必须在释放它之前设置
self->first

我不明白的部分是这一部分:

它可能有一个析构函数,导致执行尝试访问第一个成员的代码

如果

first

 是一个正在被垃圾回收的对象,则必须调用其析构函数,并且它应该可以在析构函数期间访问自身。为什么这会很危险?

谢谢!

python cpython python-c-api
1个回答
0
投票
假设我们有:

custom = Custom(...) # global variable class SomePyClass: def __del__(self): # access global variable custom.__init__(1, 2, 3)
假设 

custom.first

SomePyClass
 的实例,并且该实例在 
Py_XDECREF(self->first);
 行被破坏。

析构函数中的任意代码将导致

custom.__init__

 再次被调用。这将导致 
Py_XDECREF
 在一个确实应该有 0 的引用计数并且已经在被销毁的过程中的对象上被再次调用。仅此一点就代表了引用计数错误,因为它的引用计数最终将低于 0(请注意,这可能并不完全是发生的情况,因为在析构函数期间它暂时获得了 1 的引用计数,但它绝对是一个引用-计数错误)。

另请注意,在

first

 中分配给 
__init__
 的新值也是错误引用计数的:虽然我们的实例被减值两次,但即将分配的值将被替换,而不会减值。

© www.soinside.com 2019 - 2024. All rights reserved.