Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/python-3.x/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 指向同一地址的无效指针 问题_C++_Python 3.x_Memory Management_Cython_Void Pointers - Fatal编程技术网

C++ 指向同一地址的无效指针 问题

C++ 指向同一地址的无效指针 问题,c++,python-3.x,memory-management,cython,void-pointers,C++,Python 3.x,Memory Management,Cython,Void Pointers,指向cdef类的Void指针指向相同的内存地址,而不强制使用python引用计数器 描述 我有一个简单的类,我想通过将其转换为空指针来存储在cpp向量中。但是,在打印指针指向的内存地址之后,它会在第二次迭代后重复,除非我通过将新对象添加到列表中来强制增加引用计数器。有人能解释为什么在没有引用计数器强制的情况下内存会循环回来吗 # distutils: language = c++ # distutils: extra_compile_args = -std=c++11 from libcpp.v

指向cdef类的Void指针指向相同的内存地址,而不强制使用python引用计数器

描述 我有一个简单的类,我想通过将其转换为空指针来存储在cpp向量中。但是,在打印指针指向的内存地址之后,它会在第二次迭代后重复,除非我通过将新对象添加到列表中来强制增加引用计数器。有人能解释为什么在没有引用计数器强制的情况下内存会循环回来吗

# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
from libcpp.vector cimport vector
from libc.stdio cimport printf

cdef class Temp:
    cdef int a
    def __init__(self, a):
        self.a = a


def f():
    cdef vector[void *] vec
    cdef int i, n = 3
    cdef Temp tmp
    cdef list ids = []
    # cdef list classes  = [] # force reference counter?
    for i in range(n):
        tmp = Temp(1)
        # classes.append(tmp)
        vec.push_back(<void *> tmp)
        printf('%p ', <void *> tmp)
        ids.append(id(tmp))
    print(ids)
f()
但是,如果我通过将引用计数器添加到类列表中来强制引用计数器:

[140663518040448, 140663518040472, 140663518040496]

您的对象最终位于同一地址这一事实是巧合。您的问题是,当对python对象的最后一次python引用消失时,python对象会被销毁。如果您想让python对象保持活动状态,您需要在某个地方保存对它们的引用


在您的例子中,由于tmp是您在循环中创建的Temp对象的唯一引用,因此每次重新分配tmp时,它以前引用的对象都会被销毁。这就在内存中留下了空白空间,该空间正好适合保存在循环的下一次迭代中创建的临时对象,从而形成指针中的交替模式。

对象最终位于同一地址的事实是巧合。您的问题是,当对python对象的最后一次python引用消失时,python对象会被销毁。如果您想让python对象保持活动状态,您需要在某个地方保存对它们的引用


在您的例子中,由于tmp是您在循环中创建的Temp对象的唯一引用,因此每次重新分配tmp时,它以前引用的对象都会被销毁。这就在内存中留下了空白空间,它的大小正好可以容纳在循环的下一次迭代中创建的临时对象,从而形成指针中的交替模式。

这个答案变得相当长,因此可以快速概述内容:

对观察到的行为的解释 避免问题的天真方法 一个更系统、更典型的c++解决方案 说明nogil模式下多线程代码的问题 扩展c++——nogil模式的典型解决方案 对观察到的行为的解释

处理Cython:只要变量是object类型或继承自object类型,cdef Temp Cython就会为您管理引用计数。一旦您将其转换为PyObject*或任何其他指针,引用计数就是您的责任

显然,对已创建对象的唯一引用是变量tmp,一旦您将其重新绑定到新创建的Temp对象,旧对象的引用计数器将变为0,并被销毁-向量中的指针将悬空。然而,相同的内存可以重用,这是很有可能的,因此您总是看到相同的重用地址

原始解决方案

你怎么做参考计数?例如,我使用PyObject*而不是void*:

值得注意的是:

PyObjectHolder在拥有PyObject指针时立即增加ref计数器,在释放指针时立即减少ref计数器。 三个规则意味着我们还必须注意复制构造函数和赋值运算符 我省略了c++11的移动内容,但您也需要处理它。 nogil模式的问题

但是有一件非常重要的事情:你不应该用上面的实现来释放吉尔,即将它导入PyObjeToCultPyObjultO-Noigl,但是当C++复制向量和类似时也存在问题,因为否则PyxxPrIFF和PyxXReMGF可能无法正常工作。

来说明,让我们来看看下面的代码,它释放吉尔并并行执行一些愚蠢的计算,整个魔法单元在答案的结尾列表中:

%%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=<PyObject*>o;
    cdef int i
    for i in prange(10, nogil=True):
        create_vectors(ptr)
我们很幸运,程序没有崩溃,但可以!。然而,由于竞争条件的原因,我们最终导致了内存泄漏-一个[0]的引用计数为1177,但sys.getrefcount引用中只有1000个引用+2处于活动状态,因此这个对象永远不会被销毁

使PyObjectHolder线程安全

那怎么办呢?最简单的解决方案是使用互斥来保护对ref counter的访问。每次调用Py_XINCREF或Py_XDECREF时。这种方法的缺点是,它可能会减慢单核代码的速度,例如,有人尝试用类似互斥的方法替换GIL

这是一个原型:

%%cython --cplus -c=/openmp 
...
cdef extern from *:
    """
    #include <Python.h>
    #include <mutex>

    std::mutex ref_mutex;

    class PyObjectHolder{
    public:
        PyObject *ptr;
        PyObjectHolder():ptr(nullptr){}
        PyObjectHolder(PyObject *o):ptr(o){
            std::lock_guard<std::mutex> guard(ref_mutex);
            Py_XINCREF(ptr);
        }
        //rule of 3
        ~PyObjectHolder(){
            std::lock_guard<std::mutex> guard(ref_mutex);
            Py_XDECREF(ptr);
        }
        PyObjectHolder(const PyObjectHolder &h):
            PyObjectHolder(h.ptr){}
        PyObjectHolder& operator=(const PyObjectHolder &other){
            {
                std::lock_guard<std::mutex> guard(ref_mutex);
                Py_XDECREF(ptr);
                ptr=other.ptr;
                Py_XINCREF(ptr);
            }
            return *this;
        }
    };
    """
    cdef cppclass PyObjectHolder:
        PyObjectHolder(PyObject *o) nogil
    ...
然而,正如@DavidW所指出的,使用std::mutex仅适用于openmp线程,而不适用于Python解释器创建的线程

下面是一个互斥解决方案将失败的示例

首先,将nogil函数包装为def函数:

%%cython --cplus -c=/openmp 
...
def single_create_vectors(object o):
    cdef PyObject *ptr=<PyObject *>o
    with nogil:
         create_vectors(ptr)
使用std::mutex的另一种选择是使用Python机制,即 ld导致代码类似于

...
PyObjectHolderPy(PyObject *o):ptr(o){
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();
    Py_XINCREF(ptr);
    PyGILState_Release(gstate);
}
...
这也适用于上面的线程示例。然而,PyGILState\u确保有太多的开销-对于上面的示例,它将比互斥解决方案慢大约100倍。使用Python机器的轻量级解决方案也意味着更多的麻烦

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

%%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 <Python.h>

    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=<PyObject*>o;
    cdef int i
    for i in prange(10, nogil=True):
        create_vectors(ptr)

这个答案变得相当长,因此有一个内容的快速概述:

对观察到的行为的解释 避免问题的天真方法 一个更系统、更典型的c++解决方案 说明nogil模式下多线程代码的问题 扩展c++——nogil模式的典型解决方案 对观察到的行为的解释

处理Cython:只要变量是object类型或继承自object类型,cdef Temp Cython就会为您管理引用计数。一旦您将其转换为PyObject*或任何其他指针,引用计数就是您的责任

显然,对已创建对象的唯一引用是变量tmp,一旦您将其重新绑定到新创建的Temp对象,旧对象的引用计数器将变为0,并被销毁-向量中的指针将悬空。然而,相同的内存可以重用,这是很有可能的,因此您总是看到相同的重用地址

原始解决方案

你怎么做参考计数?例如,我使用PyObject*而不是void*:

值得注意的是:

PyObjectHolder在拥有PyObject指针时立即增加ref计数器,在释放指针时立即减少ref计数器。 三个规则意味着我们还必须注意复制构造函数和赋值运算符 我省略了c++11的移动内容,但您也需要处理它。 nogil模式的问题

但是有一件非常重要的事情:你不应该用上面的实现来释放吉尔,即将它导入PyObjeToCultPyObjultO-Noigl,但是当C++复制向量和类似时也存在问题,因为否则PyxxPrIFF和PyxXReMGF可能无法正常工作。

来说明,让我们来看看下面的代码,它释放吉尔并并行执行一些愚蠢的计算,整个魔法单元在答案的结尾列表中:

%%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=<PyObject*>o;
    cdef int i
    for i in prange(10, nogil=True):
        create_vectors(ptr)
我们很幸运,程序没有崩溃,但可以!。然而,由于竞争条件的原因,我们最终导致了内存泄漏-一个[0]的引用计数为1177,但sys.getrefcount引用中只有1000个引用+2处于活动状态,因此这个对象永远不会被销毁

使PyObjectHolder线程安全

那怎么办呢?最简单的解决方案是使用互斥来保护对ref counter的访问。每次调用Py_XINCREF或Py_XDECREF时。这种方法的缺点是,它可能会减慢单核代码的速度,例如,有人尝试用类似互斥的方法替换GIL

这是一个原型:

%%cython --cplus -c=/openmp 
...
cdef extern from *:
    """
    #include <Python.h>
    #include <mutex>

    std::mutex ref_mutex;

    class PyObjectHolder{
    public:
        PyObject *ptr;
        PyObjectHolder():ptr(nullptr){}
        PyObjectHolder(PyObject *o):ptr(o){
            std::lock_guard<std::mutex> guard(ref_mutex);
            Py_XINCREF(ptr);
        }
        //rule of 3
        ~PyObjectHolder(){
            std::lock_guard<std::mutex> guard(ref_mutex);
            Py_XDECREF(ptr);
        }
        PyObjectHolder(const PyObjectHolder &h):
            PyObjectHolder(h.ptr){}
        PyObjectHolder& operator=(const PyObjectHolder &other){
            {
                std::lock_guard<std::mutex> guard(ref_mutex);
                Py_XDECREF(ptr);
                ptr=other.ptr;
                Py_XINCREF(ptr);
            }
            return *this;
        }
    };
    """
    cdef cppclass PyObjectHolder:
        PyObjectHolder(PyObject *o) nogil
    ...
然而,正如@DavidW所指出的,使用std::mutex仅适用于openmp线程,而不适用于Python解释器创建的线程

下面是一个互斥解决方案将失败的示例

首先,将nogil函数包装为def函数:

%%cython --cplus -c=/openmp 
...
def single_create_vectors(object o):
    cdef PyObject *ptr=<PyObject *>o
    with nogil:
         create_vectors(ptr)
使用std::mutex的另一种方法是使用Python机制,也就是说,这将产生类似于

...
PyObjectHolderPy(PyObject *o):ptr(o){
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();
    Py_XINCREF(ptr);
    PyGILState_Release(gstate);
}
...
这也适用于上面的线程示例。然而,PyGILState\u确保有太多的开销-对于上面的示例,它将比互斥解决方案慢大约100倍。使用Python机器的轻量级解决方案也意味着更多的麻烦

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

%%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 <Python.h>

    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=<PyObject*>o;
    cdef int i
    for i in prange(10, nogil=True):
        create_vectors(ptr)

通过将其投射到空指针-不要!void*几乎总是错误的CDEF Temp tmp声明了一个堆栈分配的对象,将其转换为void*是非常错误的!因为每次迭代都要替换对象,所以结果就来自于它重用相同的内存位置还有其他方法可以将cdef类转换为指针吗?具体来说,我的目标是在定义的不同类上强制执行线程安全并行性。然而,在prange中,除非我直接停止gil,否则我不能直接使用python对象。将它转换成空指针是我可以从网上搜索的唯一的选择。我不太理解这个问题,但是如果你想使用指针,那么你必须使用指针,例如:CDEF TEMP*TMP=新Tun1。不幸的是,它只适用于C++类,而不是这样。主要问题是,我现在的实现是在Python代码中转换为Cython结构的Cython结构,没有C++ C++代码。为了利用多线程支持,要么必须完全重写类,要么将它们转换为指针,以便安全访问。我认为我的问题是相当利基,因为缺乏在线的例子。通过把它扔到一个空洞的指针-不要!void*几乎总是错误的CDEF Temp tmp声明
堆栈分配的对象,将其强制转换为void*是非常错误的!因为每次迭代都要替换对象,所以结果就来自于它重用相同的内存位置还有其他方法可以将cdef类转换为指针吗?具体来说,我的目标是在定义的不同类上强制执行线程安全并行性。然而,在prange中,除非我直接停止gil,否则我不能直接使用python对象。将它转换成空指针是我可以从网上搜索的唯一的选择。我不太理解这个问题,但是如果你想使用指针,那么你必须使用指针,例如:CDEF TEMP*TMP=新Tun1。不幸的是,它只适用于C++类,而不是这样。主要问题是,我现在的实现是在Python代码中转换为Cython结构的Cython结构,没有C++ C++代码。为了利用多线程支持,要么必须完全重写类,要么将它们转换为指针,以便安全访问。我认为我的问题是相当利基由于缺乏例子在线。谢谢你的例子很多!我在c/c++方面的技能是有限的,但这让我对正确转换其他一些python代码所必需的内容有了更多的了解。这可能是一个很长的问题,但您是否建议使用prange将这种方法索引到单独的类中。我正在寻找类似于multiprocessing.Pool处理它的方式的独立进程。在cython文档中,我似乎找不到正确的搜索词。@GlobalTraveler很难说,因为我不知道你到底想做什么。但是,如果您计划发布GIL即prange,则不应使用上述实现-请参阅我的上一次编辑。您能详细说明最后一部分吗?最终目标是创建N个类,其中N是线程数,这样我就可以在prange下的线程安全问题中使用它们。“仅查看/使用prange”下的对象不会被删除。@GlobalTraveler我已将解决方案扩展为线程安全的。如果您知道ref计数器不会被更改,那么您就不需要线程安全性-但有时要确保ref计数器不被触动并不容易。@DavidW您是对的,我添加了一个使用线程模块的示例,其中互斥锁失败。非常感谢您提供的示例!我在c/c++方面的技能是有限的,但这让我对正确转换其他一些python代码所必需的内容有了更多的了解。这可能是一个很长的问题,但您是否建议使用prange将这种方法索引到单独的类中。我正在寻找类似于multiprocessing.Pool处理它的方式的独立进程。在cython文档中,我似乎找不到正确的搜索词。@GlobalTraveler很难说,因为我不知道你到底想做什么。但是,如果您计划发布GIL即prange,则不应使用上述实现-请参阅我的上一次编辑。您能详细说明最后一部分吗?最终目标是创建N个类,其中N是线程数,这样我就可以在prange下的线程安全问题中使用它们。“仅查看/使用prange”下的对象不会被删除。@GlobalTraveler我已将解决方案扩展为线程安全的。如果您知道ref计数器不会被更改,那么您就不需要线程安全性-但有时要确保ref计数器不被触动并不容易。@DavidW如果您是对的,我添加了一个使用线程模块的示例,其中互斥锁失败。
%%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 <Python.h>

    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=<PyObject*>o;
    cdef int i
    for i in prange(10, nogil=True):
        create_vectors(ptr)