gpt4 book ai didi

python - 提升 Python,将 C++ 回调传播到导致段错误的 Python

转载 作者:行者123 更新时间:2023-11-30 03:50:50 26 4
gpt4 key购买 nike

我在 C++ 中有以下监听器,它接收一个 Python 对象来传播回调。

class PyClient {
private:
std::vector<DipSubscription *> subs;

subsFactory *sub;

class GeneralDataListener: public SubscriptionListener {
private:
PyClient * client;

public:
GeneralDataListener(PyClient *c):client(c){
client->pyListener.attr("log_message")("Handler created");
}

void handleMessage(Subscription *sub, Data &message) {
// Lock the execution of this method
PyGILState_STATE state = PyGILState_Ensure();
client->pyListener.attr("log_message")("Data received for topic");
...
// This method ends modifying the value of the Python object
topicEntity.attr("save_value")(valueKey, extractDipValue(valueKey.c_str(), message))
// Release the lock
PyGILState_Release(state);
}

void connected(Subscription *sub) {
client->pyListener.attr("connected")(sub->getTopicName());
}

void disconnected(Subscription *sub, char* reason) {
std::string s_reason(reason);
client->pyListener.attr("disconnected")(sub->getTopicName(), s_reason);
}

void handleException(Subscription *sub, Exception &ex) {
client->pyListener.attr("handle_exception")(sub->getTopicName())(ex.what());
}
};

GeneralDataListener *handler;

public:
python::object pyListener;


PyClient(python::object pyList): pyListener(pyList) {
std::ostringstream iss;
iss << "Listener" << getpid();
sub = Sub::create(iss.str().c_str());
createSubscriptions();
}

~PyClient() {
for (unsigned int i = 0; i < subs.size(); i++) {
if (subs[i] == NULL) {
continue;
}

sub->destroySubscription(subs[i]);
}
}
};


BOOST_PYTHON_MODULE(pytest)
{
// There is no need to expose more methods as will be used as callbacks
Py_Initialize();
PyEval_InitThreads();
python::class_<PyClient>("PyClient", python::init<python::object>())
.def("pokeHandler", &PyClient::pokeHandler);
};

然后,我有我的 Python 程序,它是这样的:

import sys
import time

import pytest


class Entity(object):
def __init__(self, entity, mapping):
self.entity = entity
self.mapping = mapping
self.values = {}
for field in mapping:
self.values[field] = ""

self.updated = False

def save_value(self, field, value):
self.values[field] = value
self.updated = True


class PyListener(object):
def __init__(self):
self.listeners = 0
self.mapping = ["value"]

self.path_entity = {}
self.path_entity["path/to/node"] = Entity('Name', self.mapping)

def connected(self, topic):
print "%s topic connected" % topic

def disconnected(self, topic, reason):
print "%s topic disconnected, reason: %s" % (topic, reason)

def handle_message(self, topic):
print "Handling message from topic %s" % topic

def handle_exception(self, topic, exception):
print "Exception %s in topic %s" % (exception, topic)

def log_message(self, message):
print message

def sample(self):
for path, entity in self.path_entity.iteritems():
if not entity.updated:
return False

sample = " ".join([entity.values[field] for field in dip_entity.mapping])
print "%d %s %d %s" % (0, entity.entity, 4324, sample)
entity.updated = False

return True


if __name__ == "__main__":
sys.settrace(trace)
py_listener = PyListener()
sub = pytest.PyClient(py_listener)

while True:
if py_listener.sample():
break

所以,最后,我的问题似乎是,当我开始在 Python 程序中运行 while True 时,脚本卡住检查实体是否更新,并且随机地,当 C++ 监听器尝试调用回调时,我得到一个段错误。

如果我只是在 python 脚本中尝试 time.sleep 并按时间调用示例时间,则相同。我知道如果我从 C++ 代码调用 sample 就会解决这个问题,但是这个脚本将由其他 Python 模块运行,该模块将在给定特定延迟的情况下调用 sample 方法。因此预期的功能将是 C++ 更新值实体和 Python 脚本来读取它们。

我已经用 gdb 调试了错误,但是我得到的堆栈跟踪并没有太多解释:

#0  0x00007ffff7a83717 in PyFrame_New () from /lib64/libpython2.7.so.1.0
#1 0x00007ffff7af58dc in PyEval_EvalFrameEx () from /lib64/libpython2.7.so.1.0
#2 0x00007ffff7af718d in PyEval_EvalCodeEx () from /lib64/libpython2.7.so.1.0
#3 0x00007ffff7af7292 in PyEval_EvalCode () from /lib64/libpython2.7.so.1.0
#4 0x00007ffff7b106cf in run_mod () from /lib64/libpython2.7.so.1.0
#5 0x00007ffff7b1188e in PyRun_FileExFlags () from /lib64/libpython2.7.so.1.0
#6 0x00007ffff7b12b19 in PyRun_SimpleFileExFlags () from /lib64/libpython2.7.so.1.0
#7 0x00007ffff7b23b1f in Py_Main () from /lib64/libpython2.7.so.1.0
#8 0x00007ffff6d50af5 in __libc_start_main () from /lib64/libc.so.6
#9 0x0000000000400721 in _start ()

如果在 Python 中使用 sys.trace 进行调试,段错误之前的最后一行总是在示例方法中,但它可能会有所不同。

我不确定如何解决此通信问题,因此非常感谢任何正确方向的建议。

编辑将 PyDipClient 引用修改为 PyClient。

发生的事情是我从 Python main 方法启动程序,如果 C++ 监听器尝试回调 Python 监听器,它会因段错误而崩溃,我认为唯一创建的线程是在我创建订阅时创建的,但这是来自库内部的代码,我不知道它是如何工作的。

如果我删除所有对 Python 监听器的回调,并强制使用 Python 中的方法(例如调用 pokehandler),一切都会完美运行。

最佳答案

最有可能的罪魁祸首是 Global Interpreter Lock (GIL) 在调用 Python 代码时未被线程占用,导致未定义的行为。验证进行 Python 调用的所有路径,例如 GeneralDataListener的函数,在调用 Python 代码之前获取 GIL。如PyClient份正在制作,那么pyListener需要以允许 GIL 在被复制和销毁时保留的方式进行管理。

此外,考虑 rule of three对于 PyClient .复制构造函数和赋值运算符是否需要对订阅做任何事情?


GIL 是 CPython 解释器周围的互斥量。此互斥锁可防止对 Python 对象执行并行操作。因此,在任何时间点,最多允许一个线程(已获得 GIL 的线程)对 Python 对象执行操作。当存在多个线程时,在不持有 GIL 的情况下调用 Python 代码会导致未定义的行为。

C 或 C++ 线程有时在 Python 文档中称为外来线程。 Python 解释器没有能力控制外来线程。因此,外来线程负责管理 GIL 以允许与 Python 线程并发或并行执行。

在当前代码中:

  • GeneralDataListener::handle_message()以非异常安全的方式管理 GIL。例如,如果监听器的 log_message()方法抛出异常,堆栈将展开而不释放 GIL 作为 PyGILState_Release()不会被调用。

    void handleMessage(...)
    {
    PyGILState_STATE state = PyGILState_Ensure();
    client->pyListener.attr("log_message")(...);
    ...

    PyGILState_Release(state); // Not called if Python throws.
    }
  • GeneralDataListener::connected() , GeneralDataListener:: disconnected() , 和 GeneralDataListener:: handleException()显式调用 Python 代码,但不显式管理 GIL。如果调用者不拥有 GIL,则在没有 GIL 的情况下执行 Python 代码时会调用未定义的行为。

    void connected(...)
    {
    // GIL not being explicitly managed.
    client->pyListener.attr("connected")(...);
    }
  • PyClient的隐式创建的复制构造函数和赋值运算符不管理 GIL,但在复制 pyListener 时可能会间接调用 Python 代码。数据成员。如果正在制作拷贝,则调用者需要在 PyClient::pyListener 时持有 GIL。正在复制和销毁对象。如果pyListener未在可用空间上进行管理,那么调用者必须了解 Python 并且在整个 PyClient 的销毁过程中获得了 GIL对象。

要解决这些问题,请考虑:

  • 使用 Resource Acquisition Is Initialization (RAII) 守卫类以异常安全的方式帮助管理 GIL。例如,对于以下 gil_lock 类,当创建 gil_lock 对象时,调用线程将获取 GIL。当 gil_lock 对象被销毁时,它释放 GIL

    /// @brief RAII class used to lock and unlock the GIL.
    class gil_lock
    {
    public:
    gil_lock() { state_ = PyGILState_Ensure(); }
    ~gil_lock() { PyGILState_Release(state_); }
    private:
    PyGILState_STATE state_;
    };

    ...

    void handleMessage(...)
    {
    gil_lock lock;
    client->pyListener.attr("log_message")(...);
    ...
    }
  • 在从外来线程中调用 Python 代码的任何代码路径中显式管理 GIL。

    void connected(...)
    {
    gil_lock lock;
    client->pyListener.attr("connected")(...);
    }
  • 制作 PyClient不可复制或显式创建复制构造函数和赋值运算符。如果正在制作拷贝,则更改 pyListener由允许在 GIL 被持有时显式销毁的类型持有。一种解决方案是使用 boost::shared_ptr<python::object>管理 python::object 的拷贝提供给 PyClient在构造期间,并有一个 GIL 感知的自定义删除器。或者,可以使用类似 boost::optional 的东西.

    class PyClient
    {
    public:

    PyClient(const boost::python::object& object)
    : pyListener(
    new boost::python::object(object), // GIL locked, so copy.
    [](boost::python::object* object) // Delete needs GIL.
    {
    gil_lock lock;
    delete object;
    }
    )
    {
    ...
    }

    private:
    boost::shared_ptr<boost::python::object> pyListener;;
    };

    请注意,通过管理 boost::python::object在自由空间,可以自由复制shared_ptr没有持有 GIL。另一方面,如果有人使用类似 boost::optional 的东西要管理 Python 对象,则需要在复制构造、分配和销毁期间持有 GIL。

考虑阅读 this回答有关回调到 Python 的更多细节和微妙的细节,例如复制构造和销毁期间的 GIL 管理。

关于python - 提升 Python,将 C++ 回调传播到导致段错误的 Python,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31556640/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com