2023年7月10日发(作者:)
CC++多线程调⽤嵌⼊Python完整流程最近都很忙,忙着把公司的Python回测框架完成。前两天公司同事抱怨 C/C++调⽤Python超级烦⼈,动不动就返回NULL,⽽且内存暴涨,于是我决定尝试解决这个问题,提供⼀套完整的开发流程,供⼤家技术分享。要完成C/C++调⽤Python最好是熟悉C/C++和Python,否则出了问题就⽐较难解决。之前没有使⽤过 C/C++ 调⽤嵌⼊Python,只⽤过 Cython 编写Python 扩展程序。基本是从零开始学习,但我并不想快速完成任务,否则随便度娘⼀下就OK,事实上,那样做虽能快速解决问题,但只知其⼀不知其⼆还是⽐较⼼虚。于是从开始看起。Visual Studio / Python 环境搭建在各⼤操作系统上安装Python时,同时安装了 C++开发资源,包括: Include⽂件,静态链接库⽂件,动态链接库⽂件。标准的官⽅⽂档包含了 Python/C API 以及 Extending and Embedding 主题⽂档。VS2015+Python3.6.1, 我直接⽤我所建⽴的⼯程来讲解,请根据⾃⼰实际情况修改。官⽹下载安装 Python3 相应版本官⽹下载 ⽂件
解压之后拷贝到⼯程⽣成exe所在⽬录, 注意 与⽣成exe⽬录同级。VS新建项⽬, 设置项⽬ Python 头⽂件路径
配置属性>C/C++>常规>附加包含⽬录
你的Python安装⽬录include , ⽐如我的:D:CodeToolPythonPython36include复制 到 cpp ⽂件所在⽬录,设置项⽬属性⽅式设置 lib 路径D:修改 pyconfig.h ⽂件,Debug ⼯程不会提⽰找不到
python36_e 337 左右, 增加
//#ifdef _DEBUG//# define Py_DEBUG#endifline 292 左右 ,修改 python36_# if defined(_DEBUG)# pragma comment(lib,"")//# pragma comment(lib,"python36_")# elif defined(Py_LIMITED_API)# pragma comment(lib,"")# else# pragma comment(lib,"")# endif /* _DEBUG */C++调⽤Python的接⼝⽰例
通过
#pragma comment 指令引⼊ lib 库#include
{ pFunc = PyObject_GetAttrString(pModule, module_method); if (pFunc && PyCallable_Check(pFunc))
{ pArgs = PyTuple_New(7); PyTuple_SetItem(pArgs, 0, use_int); PyTuple_SetItem(pArgs, 1, use_str); PyTuple_SetItem(pArgs, 2, use_byte); PyTuple_SetItem(pArgs, 3, use_list); PyTuple_SetItem(pArgs, 4, use_tuple); PyTuple_SetItem(pArgs, 5, use_dict); PyTuple_SetItem(pArgs, 6, use_complex); pValue = PyObject_CallObject(pFunc, pArgs); Py_DECREF(pArgs); if (pValue != NULL) { int ret_int; char *ret_str, *ret_byte; PyObject* ret_list, *ret_tuple, *ret_dict, *ret_complex; //解析元组 PyArg_ParseTuple(pValue, "isyOOOO", &ret_int, &ret_str, &ret_byte, &ret_list,&ret_tuple,&ret_dict,&ret_complex); Py_DECREF(pValue); } else { Py_DECREF(pFunc); Py_DECREF(pModule); PyErr_Print(); fprintf(stderr, "Call failedn"); } } else { if (PyErr_Occurred()) PyErr_Print(); fprintf(stderr, "Cannot find function "%s"n", module_method); } } Py_XDECREF(pFunc); Py_DECREF(pModule); } else { PyErr_Print(); fprintf(stderr, "Failed to load "%s"n", module_name); } Py_FinalizeEx();}int main(int argc, char *argv[]){ test_use_multi_param(); system("pause");}这是⼀个调⽤ Python 函数的基本⽤法,其中包含了⼏个阶段:Py_Initialize - Py_FinalizeExPy模块加载,Py函数加载,Py函数参数构造,调⽤Py函数,获取Py函数返回,变量引⽤计数处理/ 错误处理变量引⽤计数管理,请直接参考
C/C++ 使⽤Python对象,对于引⽤计数⼀定要如履薄冰,否则就会出现内存泄漏。C++多线程调⽤嵌⼊Python在我们公司⾥,C++程序会运⾏嵌⼊Pyhton作为扩展接⼝。在C++多线程环境下,直接调⽤ api操作 Python解释器,肯定会导致
coredump, 因为 Python 绝⼤部分函数都是⾮线程安全的。由GIL控制访问顺序。启⽤线程⽀持Py_Initialize();PyEval_InitThreads();// 其它代码Py_FinalizeEx();编译解释器库时启⽤了多线程⽀持(VS默认⽀持),才能使⽤ PyEval_InitThreads, 如果你的程序不需要多线程,那么建议关闭多线程⽀持。线程状态与全局解释器锁(GIL)Python解释器不是完全线程安全的。为了⽀持多线程Python程序,有⼀个全局锁,称为 global interpreter lock or GIL,在当前线程能够安全访问Python对象之前,它必须由当前线程持有。没有锁,即使是最简单的操作也可能导致多线程程序中的问题:例如,当两个线程同时增加相同对象的引⽤计数时,引⽤计数可能最终只增加⼀次,⽽不是增加两次。因此,存在这样的规则,即只有获取了GIL的线程可以操作Python对象或调⽤Python/C API函数。为了模拟执⾏的并发性,解释程序经常尝试切换线程(参见tchinterval())。该锁还围绕可能阻塞I/O操作(如读取或写⼊⽂件)释放,以便其他Python线程可以同时运⾏。Python解释器将⼀些特定于线程的簿记信息保存在称为PyThreadState的数据结构内。还有⼀个全局变量指向当前的PyThreadState状态:它可以使⽤PyThreadState_Get()检索。从扩展代码执⾏释放GILPy_BEGIN_ Do some blocking I/O operation ...Py_END_ALLOW_THREADS以上宏实际展开PyThreadState *_save_save = PyEval_SaveThread()...Do some blocking I/PyEval_RestoreThread(_save)⾮Python创建的线程如果需要从第三⽅即⾮Python创建线程调⽤Python代码(通常这将是上述第三⽅库提供的回调API的⼀部分),则必须⾸先通过创建线程状态数据结构来向解释器注册这些线程,然后获取GIL,最后存储它们的线程状态指针,然后可以开始使⽤Python /C API。完成后,您应该重置线程状态指针,释放GIL,并最终释放线程状态数据结构。PyGILState_Ensure()和PyGILState_Release()函数⾃动执⾏上述所有操作。从C线程调⽤Python的典型习惯⽤法是:PyGILState_STATE gstate;gstate = PyGILState_Ensure();/* Perform Python actions here. */result = CallSomeFunction();/* evaluate result or handle exception *//* Release the thread. No Python API allowed beyond this point. */PyGILState_Release(gstate);注意:PyGILState_xx()函数假设只有⼀个全局解释器(由Py_Initialize()⾃动创建)。Python⽀持创建额外的解释器(使⽤Py_NewInterpreter()),但不⽀持混合多个解释器和PyGILState_xx() API。根据上⾯官⽅⽂档,就可以轻易写出相关代码了。// 封装PyGILState_Ensure/PyGILState_Releaseclass PythonThreadLocker{ PyGILState_STATE state;public: PythonThreadLocker() : state(PyGILState_Ensure()) {} ~PythonThreadLocker() { PyGILState_Release(state); }};int CallSomeFunction(){ int argc = 5; char *argv[] = { "", "multiply", "multiply", "3", "2" }; PyObject *pName, *pModule, *pFunc; PyObject *pArgs, *pValue; int i; pName = PyUnicode_DecodeFSDefault(argv[1]); /* Error checking of pName left out */ pModule = PyImport_Import(pName); Py_DECREF(pName); if (pModule != NULL) { pFunc = PyObject_GetAttrString(pModule, argv[2]); /* pFunc is a new reference */ if (pFunc && PyCallable_Check(pFunc)) { pArgs = PyTuple_New(argc - 3); for (i = 0; i < argc - 3; ++i) { pValue = PyLong_FromLong(atoi(argv[i + 3])); pValue = PyLong_FromLong(atoi(argv[i + 3])); if (!pValue) { Py_DECREF(pArgs); Py_DECREF(pModule); fprintf(stderr, "Cannot convert argumentn"); return 1; } /* pValue reference stolen here: */ PyTuple_SetItem(pArgs, i, pValue); } pValue = PyObject_CallObject(pFunc, pArgs); Py_DECREF(pArgs); if (pValue != NULL) { printf("Result of call: %ldn", PyLong_AsLong(pValue)); Py_DECREF(pValue); } else { Py_DECREF(pFunc); Py_DECREF(pModule); PyErr_Print(); fprintf(stderr, "Call failedn"); return 1; } } else { if (PyErr_Occurred()) PyErr_Print(); fprintf(stderr, "Cannot find function "%s"n", argv[2]); } Py_XDECREF(pFunc); Py_DECREF(pModule); } else { PyErr_Print(); fprintf(stderr, "Failed to load "%s"n", argv[1]); return 1; } return 0;}void use_thread_a(){ PythonThreadLocker locker; int result = CallSomeFunction();}// 创建线程int main(int argc, char *argv[]){ Py_Initialize(); PyEval_InitThreads(); printf("%d", PyEval_ThreadsInitialized()); printf("a%dn", PyGILState_Check()); Py_BEGIN_ALLOW_THREADS printf("b%dn", PyGILState_Check()); std::thread t1(use_thread_a); std::thread t2(use_thread_a); std::thread t3(use_thread_a); std::thread t4(use_thread_a); std::thread t5(use_thread_a); (); (); (); (); (); (); printf("c%dn", PyGILState_Check()); Py_END_ALLOW_THREADS printf("d%dn", PyGILState_Check()); CallSomeFunction(); Py_FinalizeEx(); return 0;} ⽂件def multiply(a,b): print("Will compute", a, "times", b) c = 0 for i in range(0, a): c = c + b return cdef hello2(): print('hello')def tset_use_pd(): import pandas as pd print(ame({'a':[1,2,3],'b':[4,5,6]}))def test_raise_error(): raise ValueError('test raise valueerror')def test_use_mulit_params(use_int, use_str: str, use_byte: bytes, use_list: list, use_tuple: tuple, use_dict: dict, use_complex): print('use_int', use_int) print('use_str', use_str) print('use_byte', use_byte) print('use_list', use_list) print('use_tuple', use_tuple) print('use_dict', use_dict) print('use_complex', use_complex) return (use_int, use_str, use_byte, use_list, use_tuple, use_dict, use_complex)思考作为⼀名前⾏的软件⼯程师,需要不断思考学习积累,绝不能急于求成,⼼浮⽓躁。随便百度搜索答案。虽然⼀天只做了⼀件事,但也是值得的。通过阅读官⽅⽂档,分析与实践同⾏,充分理解其含义,体会深刻。不然永远都不会明⽩程序为什么会 core dumps, wrongresults, mysterious crashes。加好友 &如果你和我有共同爱好,加好友⼀起学习吧!
发布者:admin,转转请注明出处:http://www.yc00.com/web/1688930670a184706.html
评论列表(0条)