java 指向同一地址_无效指针指向同一地址

这个答案变得很长,因此可以快速浏览内容:

观察到的行为的说明

天真的方法来避免这个问题

更系统,更典型的解决方案

解释了"nogil" -mode中多线程代码的问题

为nogil模式扩展c-ticalpical解决方案

Explanation of the observed behavior

与Cython的交易:只要您的变量属于 object 类型或从它继承(在您的情况下 cdef Temp ),cython将为您管理引用计数 . 只要将其转换为 PyObject * 或任何其他指针 - 引用计数就是您的责任 .

显然,对创建的对象的唯一引用是变量 tmp ,只要将其重新绑定到新创建的 Temp object,旧对象的引用计数器就会变为 0 并且它被销毁 - 向量中的指针变得悬空 . 但是,可以重用相同的内存(很可能),因此您总是看到相同的重用地址 .

Naive solution

你怎么能做引用计数?例如(我使用 PyObject * 而不是 void * ):

...

from cpython cimport PyObject,Py_XINCREF, Py_XDECREF

...

def f():

cdef vector[PyObject *] vec

cdef int i, n = 3

cdef Temp tmp

cdef PyObject *tmp_ptr

cdef list ids = []

for i in range(n):

tmp = Temp(1)

tmp_ptr = tmp

Py_XINCREF(tmp_ptr) # ensure it is not destroyed

vec.push_back(tmp_ptr)

printf('%p ', tmp_ptr)

ids.append(id(tmp))

#free memory:

for i in range(n):

Py_XDECREF(vec.at(i))

print(ids)

现在所有对象都保持活动状态"die"只有在显式调用 Py_XDECREF 之后 .

C++-typical solution

以上不是一个非常典型的做法,我宁愿介绍一个自动管理引用计数的包装器(与 std::shared_ptr 不同):

...

cdef extern from *:

"""

#include

class PyObjectHolder{

public:

PyObject *ptr;

PyObjectHolder():ptr(nullptr){}

PyObjectHolder(PyObject *o):ptr(o){

Py_XINCREF(ptr);

}

//rule of 3

~PyObjectHolder(){

Py_XDECREF(ptr);

}

PyObjectHolder(const PyObjectHolder &h):

PyObjectHolder(h.ptr){}

PyObjectHolder& operator=(const PyObjectHolder &other){

Py_XDECREF(ptr);

ptr=other.ptr;

Py_XINCREF(ptr);

return *this;

}

};

"""

cdef cppclass PyObjectHolder:

PyObjectHolder(PyObject *o)

...

def f():

cdef vector[PyObjectHolder] vec

cdef int i, n = 3

cdef Temp tmp

cdef PyObject *tmp_ptr

cdef list ids = []

for i in range(n):

tmp = Temp(1)

vec.push_back(PyObjectHolder( tmp)) # vector::emplace_back is missing in Cython-wrappers

printf('%p ', tmp)

ids.append(id(tmp))

print(ids)

# PyObjectHolder automatically decreases ref-counter as soon

# vec is out of scope, no need to take additional care

值得注意的事情:

PyObjectHolder 只要拥有一个 PyObject -pointer就会增加ref-counter,并在释放指针后立即减少它 .

三条法则意味着我们还必须注意复制构造函数和赋值运算符

我省略了针对c 11的移动内容,但你也需要处理它 .

Problems with nogil-mode

然而,有一个非常重要的事情: You shouldn't release GIL 具有上述实现(即将其导入 PyObjectHolder(PyObject *o) nogil 但是当C复制向量时也存在问题) - 因为否则 Py_XINCREF 和 Py_XDECREF 可能无法正常工作 .

为了说明这一点,我们来看看下面的代码,它释放gil并且并行执行一些愚蠢的计算(整个魔术单元在答案的最后是列表中):

%%cython --cplus -c=/openmp

...

# importing as nogil - A BAD THING

cdef cppclass PyObjectHolder:

PyObjectHolder(PyObject *o) nogil

# some functionality using a lot of incref/decref

cdef int create_vectors(PyObject *o) nogil:

cdef vector[PyObjectHolder] vec

cdef int i

for i in range(100):

vec.push_back(PyObjectHolder(o))

return vec.size()

# using PyObjectHolder without gil - A BAD THING

def run(object o):

cdef PyObject *ptr=o;

cdef int i

for i in prange(10, nogil=True):

create_vectors(ptr)

现在:

import sys

a=[1000]*1000

print("Starts with", sys.getrefcount(a[0]))

# prints: Starts with 1002

run(a[0])

print("Ends with", sys.getrefcount(a[0]))

#prints: Ends with 1177

我们很幸运,程序没有崩溃(但可能!) . 但是由于竞争条件,我们最终导致内存泄漏 - a[0] 的引用计数为 1177 但是只有1000个引用( sys.getrefcount 内部有2个)引用,因此该对象永远不会被销毁 .

Making PyObjectHolder thread-safe

那么该怎么办?最简单的解决方案是使用互斥锁来保护对ref-counter的访问(即每次调用 Py_XINCREF 或 Py_XDECREF 时) . 这种方法的缺点是它可能会显着降低单核代码的速度(例如,参见this old article关于通过类似互斥的方法替换GIL的旧尝试) .

这是一个原型:

%%cython --cplus -c=/openmp

...

cdef extern from *:

"""

#include

#include

std::mutex ref_mutex;

class PyObjectHolder{

public:

PyObject *ptr;

PyObjectHolder():ptr(nullptr){}

PyObjectHolder(PyObject *o):ptr(o){

std::lock_guard<:mutex> guard(ref_mutex);

Py_XINCREF(ptr);

}

//rule of 3

~PyObjectHolder(){

std::lock_guard<:mutex> guard(ref_mutex);

Py_XDECREF(ptr);

}

PyObjectHolder(const PyObjectHolder &h):

PyObjectHolder(h.ptr){}

PyObjectHolder& operator=(const PyObjectHolder &other){

{

std::lock_guard<:mutex> guard(ref_mutex);

Py_XDECREF(ptr);

ptr=other.ptr;

Py_XINCREF(ptr);

}

return *this;

}

};

"""

cdef cppclass PyObjectHolder:

PyObjectHolder(PyObject *o) nogil

...

现在,运行从上面剪切的代码会产生预期/正确的行为:

import sys

a=[1000]*1000

print("Starts with", sys.getrefcount(a[0]))

# prints: Starts with 1002

run(a[0])

print("Ends with", sys.getrefcount(a[0]))

#prints: Ends with 1002

列出完整的线程不安全版本:

%%cython --cplus -c=/openmp

from libcpp.vector cimport vector

from libc.stdio cimport printf

from cpython cimport PyObject

from cython.parallel import prange

import sys

cdef extern from *:

"""

#include

class PyObjectHolder{

public:

PyObject *ptr;

PyObjectHolder():ptr(nullptr){}

PyObjectHolder(PyObject *o):ptr(o){

Py_XINCREF(ptr);

}

//rule of 3

~PyObjectHolder(){

Py_XDECREF(ptr);

}

PyObjectHolder(const PyObjectHolder &h):

PyObjectHolder(h.ptr){}

PyObjectHolder& operator=(const PyObjectHolder &other){

{

Py_XDECREF(ptr);

ptr=other.ptr;

Py_XINCREF(ptr);

}

return *this;

}

};

"""

cdef cppclass PyObjectHolder:

PyObjectHolder(PyObject *o) nogil

cdef int create_vectors(PyObject *o) nogil:

cdef vector[PyObjectHolder] vec

cdef int i

for i in range(100):

vec.push_back(PyObjectHolder(o))

return vec.size()

def run(object o):

cdef PyObject *ptr=o;

cdef int i

for i in prange(10, nogil=True):

create_vectors(ptr)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值