最近跟同学一起做后台的项目,用的c语言。由于要用到python下的算法库,因此学习了在c程序中如何调用python程序。
由于后台的环境用到了多线程,因此需要考虑多线程环境下的c调用python。
在多线程环境下调用python库,需要按照如下步骤:
在主线程中:
1、初始化python,允许支持多线程。
2、python扩展初始化
3、释放线程全局锁
在子线程中:
4、设置GIL(python特有的全局解释锁)的状态,确保为当前线程控制GIL
5、调用ython代码
6、释放GIL,将GIL的控制权交还给系统
功能描述:我们需要在c代码中调用python函数,并且,要将c的参数传到python中,并解析python的返回值存入c的变量中。
示例:
#include<Python.h>
PyObject * pFunc = NULL; //全局变量,可使process.c文件访问该变量
int pythonInit() //自定义python初始化,包括python初始化以及相关模块参数的导入。
{
Py_Initialize(); //python初始化,库函数
if (!Py_IsInitialized())
{
printf(“Python init failed!\n”);
PyErr_Print();//打印错误信息,库函数
return -1;
}
PyObject *pDict = NULL; //python模块字典
PyObject *pModule = NULL; //python模块
PyRun_SimpleString(“import sys”); //调用python代码
PyRun_SimpleString(“sys.path.append(‘./’)”); //将当前目录添加到python路径
//导入模块,模块名为query_python。在query_python.py文件中。
pModule = PyImport_Import(PyString_FromString(“query_python”));
if(!pModule)
{
printf(“Load query_python.py failed!\n”);
PyErr_Print();
return -1;
}
pDict = PyModule_GetDict(pModule); //调用库函数获取模块字典,模块字典包含该模块下的所有函数
if(!pDict)
{
printf(“Can’t find dict in query_python!\n”);
PyErr_Print();
return -1;
}
pFunc = PyDict_GetItemString(pDict, “query_k”);//获取模块字典中query_k函数
if(!pFunc || !PyCallable_Check(pFunc)) //检查函数的有效性
{
printf(“Can’t find function!\n”);
PyErr_Print();
return -1;
}
if(pModule) //释放模块申请的内存
{
Py_DECREF(pModule);
}
if(pDict) //释放模块字典申请的内存
{
Py_DECREF(pDict);
}
return 0;
}
//主调函数
void serverStart()
{
PyEval_InitThreads();//初始化多线程,使python支持多线程
int res;
res = pythonInit(); //python扩展初始化
if(res==-1)
{
PyErr_Print();
return;
}
//开启多线程之前,必须先释放锁,才能获得线程全局锁
PyEval_ReleaseLock();
… //此处开启了多线程
process();//process函数会调用process.c中的get_query_result函数
PyGILState_Ensure(); //主线程结束,释放GIL
Py_Finalize(); //对应于python初始化的释放
}
process.c:
extern PyObject *pFunc; //获取另一个文件中的全局变量
///下面的函数功能是获取query_python.py文件中的query_k函数的返回值,并传给c程序的query_res指向的内存。
int get_query_result(const char *buf, const char *query, int k, char *query_res)
{
PyGILState_STATE gstate = PyGILState_Ensure();//确保当前线程获得GIL的控制权
if (!PyCallable_Check(pFunc))
{
PyErr_Print();
return -1;
}
PyObject *pArgs = NULL; //传给PyObject_Call函数的第二个参数
PyObject *pkwargs = NULL; //传给PyObject_Call函数的第三个参数
pArgs = PyTuple_New(3); //函数参数以元组形式传入,参数个数为3
PyTuple_SetItem(pArgs, 0, Py_BuildValue(“s”, query)); //传给python的参数,s代表字符串,i代表整数
PyTuple_SetItem(pArgs, 1, Py_BuildValue(“s”, buf));
PyTuple_SetItem(pArgs, 2, Py_BuildValue(“i”, k));
PyObject *pyValue = PyObject_Call(pFunc, pArgs, pkwargs);//传入pFunc指向的函数,参数。返回值存入pyValue中。
if (pyValue == NULL)
{
printf(“PyObject_CallObject error!\n”);
PyErr_Print();
PyGILState_Release(gstate); //返回值为NULL,需要释放GIL
return -1;
}
char *ret_str; //无需申请内存
int len;
PyArg_ParseTuple(pyValue, “si”, &ret_str, &len); //解析返回值,将返回值存入ret_str字符数组和len变量中。
strncpy(query_res, ret_str, len); //执行字符串拷贝。
if(pArgs)
{
Py_DECREF(pArgs); //释放参数内存
}
if(pkwargs) //释放参数内存
{
Py_XDECREF(pkwargs);
}
PyGILState_Release(gstate); //释放GIL
return 0;
}
总结:
第一次学习c调用python,查阅了大量资料,花了不少时间,收获还是不少。
调试过程中遇到了很多问题,暴露了c语言的一些不足,同时,尽管python学了一段时间了,但对pyhton的GIL理解不深。