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

Python调用C++异步行为:GIL引发的同步问题及代码安全性验证

C++异步类Python调用的安全性分析

问题背景

我需要用C封装异步行为,通过pybind11/stl.h实现数据交互,不共享Python与C内存,仅传递C类的Python实例。此前在C绑定函数中运行异步任务无异常(C主函数与Python主函数结果一致),但以Python为主程序时,C异步线程出现同步问题。不确定该继续调试代码,还是GIL导致多线程下C++对象使用异常。

我认为Python与C++共用实例且多线程运行可能存在风险,目标实现:

  • C++类包含三个方法:创建线程并保存句柄到类作用域;返回布尔值表示计算是否完成;返回缓存的计算结果
  • Python端周期性调用检查方法,确认计算完成后获取结果

示例代码

C++代码(供Python调用)

#include <vector>
#include <thread>
#include <atomic>
#include <iostream>

class DummyClass {
private:
  std::vector<std::vector<double>> _data;
  std::atomic<bool> _isDone{false};
  const size_t _target = 100;
  std::vector<std::thread> _workers;

public:
  DummyClass() = default;

  ~DummyClass() {
      for (auto& t : _workers)
          t.join();

      _workers.clear();
  }
  void do_dummy_async_task() {
       auto dummy_task = [&]() {
         using namespace std::chrono_literals;
         std::cout << "Dummy task started" << std::endl;
         // 模拟耗时操作,修改类成员_data
         for (int i = 0; i < _target; i++) {
              std::this_thread::sleep_for(5ms);
              _data.push_back(std::vector<double>(1000, 0)); 
         }
         std::cout << "Dummy Task ended" << std::endl;
         _isDone = true;
       };

      _workers.emplace_back(dummy_task);
      return;
  }
  bool isDone() {
      return _isDone;
  }
  std::vector<std::vector<double>> get_results(){
      return _data;
  }
  size_t get_number_of_targets() {
      return _target;
  }
};

Python主程序

from pyDummy import DummyClass
import time

def run_main():
   obj = DummyClass()
   expecting = obj.get_number_of_results()  # 注意:C++方法名为get_number_of_targets,此处名称不匹配
   obj.do_async_task()  # 注意:C++方法名为do_dummy_async_task,此处名称不匹配

   print("Doing python things while Compiled Code does something else")

   while(not obj.is_Done()):  # 注意:C++方法名为isDone,此处名称不匹配
     time.sleep(1)

   results = obj.get_results()

   print(f"Done:\nExpected {expecting} and got {len(results)}!")    

if __name__ == "__main__":
   run_main()   

代码安全性分析

核心结论:现有代码存在线程安全风险,并非GIL直接导致,但需修正同步逻辑与方法名匹配问题

  1. GIL的影响
    C线程运行时不会持有GIL(pybind11仅在Python调用C函数时临时获取GIL,C自主创建的线程默认不持有),因此GIL不会干扰C线程对类成员的操作。Python调用C的isDone()、get_results()等方法时,会短暂获取GIL,但这些方法本身是读操作或返回拷贝,不会阻塞C线程。

  2. 线程安全隐患

  • _isDone使用std::atomic<bool>,读写操作原子化,无线程安全问题。
  • _data在C++异步线程中执行push_back,而Python调用get_results()时会拷贝整个vector。std::vector::push_back在扩容时会导致内存重新分配,若此时Python端正在拷贝_data,会触发未定义行为(如拷贝到失效的内存区域)。此为核心风险点。
  • 析构函数中调用join()等待所有线程完成,这部分逻辑安全,避免了对象销毁时线程仍在运行的问题。
  1. 代码中的命名错误
    Python代码中调用的方法名与C类的方法名不匹配(如get_number_of_results()对应C的get_number_of_targets()),会直接导致运行时错误,需修正为与pybind11绑定后的名称一致。

修正建议

  1. 添加互斥锁保护共享数据
    给_data添加std::mutex,在修改和读取时加锁:
// DummyClass私有成员添加
std::mutex _data_mutex;

// 异步任务中修改_data时加锁
std::lock_guard<std::mutex> lock(_data_mutex);
_data.push_back(std::vector<double>(1000, 0));

// get_results中拷贝数据时加锁
std::lock_guard<std::mutex> lock(_data_mutex);
return _data;
  1. 修正Python端方法名
    确保Python调用的方法名与C++绑定的名称一致,比如:
expecting = obj.get_number_of_targets()
obj.do_dummy_async_task()
while(not obj.isDone()):
  1. 可选:结果完整性校验
    在get_results()中可检查_isDone状态,避免返回未完成的结果:
std::vector<std::vector<double>> get_results(){
    if (!_isDone) {
        return {}; // 或抛出异常
    }
    std::lock_guard<std::mutex> lock(_data_mutex);
    return _data;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 05:43:22