You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用pybind11时Python unittest执行成功后卡在sys.exit(0)

Pybind11回调导致Python unittest无法正常退出的问题

问题背景

用pybind11实现C++-Python接口,注册Python回调函数后,unittest测试执行成功,但无法正常退出,卡在sys.exit(0)步骤,仅能通过os._exit(0)强制终止进程。


C++ 接口实现

pybind_callback.h

#pragma once

#include <stdint.h>

#include <functional>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>

namespace py = pybind11;

typedef std::function<void(uint32_t, uint32_t)> tCbType;

void pybind_add_cb(tCbType func);

pybind_callback.cpp

#include "pybind_callback.h"
#include <vector>

static std::vector<tCbType> cb_func{};

void pybind_add_cb(tCbType func)
{
    cb_func.push_back(func);
}

Pybind11绑定代码(main.cpp)

#include <pybind11/pybind11.h>

#include "pybind_callback.h"

namespace py = pybind11;

PYBIND11_MODULE(pybind_interface, m) {
    m.def("pybind_add_cb", &pybind_add_cb, "Register a callback function");
}

Python 测试代码(test.py)

import unittest

from pybind_interface import *

class TestPyBind11(unittest.TestCase):
    def setUp(self):
        self.cb_count = 0
        print("Starting test!")
    
    def cb_func(self, start, end):
        print(f"cb_func(start=0x{start:08x}, end=0x{end:08x})")
        self.cb_count += 1
    
    def test_callback(self):
        pybind_add_cb(lambda start,end: self.cb_func(start, end))

if __name__ == '__main__':
    unittest.main()

问题现象

测试执行完成后输出如下内容,但卡在OK之后无法退出:

Starting test!
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
*卡顿在此处*

确认self.result.wasSuccessful()返回1,本该执行sys.exit(0)正常退出,但实际进程无响应。替换为os._exit(0)可直接终止进程。


原因分析

核心问题是C++侧全局static容器持有Python回调的强引用,导致Python退出时资源清理阻塞:

  • Pybind11将Python函数(这里是捕获了测试实例self的lambda)包装为std::function时,会创建一个包含Python对象引用的wrapper。
  • 这个wrapper被存入全局static的cb_func向量中,Python解释器退出时,全局static变量的销毁顺序晚于Python核心资源的清理流程。当Python尝试回收测试实例等对象时,发现C++侧还持有强引用,无法完成销毁,进而引发退出流程阻塞。
  • sys.exit(0)会触发Python的完整退出流程(包括对象析构、资源回收),而这个未释放的引用打破了流程;os._exit(0)直接终止进程,跳过了Python的资源清理,所以能正常退出。

解决方案

方法1:添加回调清理接口(推荐)

在C++侧提供清空回调列表的函数,释放对Python对象的引用:

  1. 修改pybind_callback.h,新增声明:
    void pybind_clear_cbs();
    
  2. 修改pybind_callback.cpp,实现函数:
    void pybind_clear_cbs()
    {
        cb_func.clear();
    }
    
  3. 在main.cpp中绑定该函数:
    m.def("pybind_clear_cbs", &pybind_clear_cbs, "Clear all registered callbacks");
    
  4. 在Python测试类中添加tearDown方法,测试结束后清理回调:
    def tearDown(self):
        pybind_clear_cbs()
    

方法2:避免全局static存储

将cb_func从全局static改为类成员变量,通过绑定类的方式管理回调,确保在Python退出前该类实例被销毁,自动释放引用。

方法3:使用弱引用(复杂不推荐)

自定义wrapper将Python函数的强引用转为弱引用,但需要额外处理回调有效性检查,实现复杂度较高,仅适用于不需要回调长期存活的场景。


内容的提问来源于stack exchange,提问作者Anoushka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:53:28