在c++多线程环境中嵌入python

要解决的问题:

在c++中嵌入python解释器,可以执行python脚本,且可以开启多个c++线程来并行执行python脚本。分别使用python的CAPI和结合pybind11 来使用。

测试环境:

Deepin 15.11 / Ubuntu 16.04

python3.5

 

如果使用Python CAPI, 代码如下 EmbedPyInCpp.cpp  

#include 
#include 
#include 

void Test()
{
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();
    const char *filename = "Embed.py";
    FILE *f;
    f = fopen(filename, "r");
    PyRun_SimpleFile(f, filename);
    // PyObject *pdict = PyDict_New();
    // PyObject *pkey = PyBytes_FromString("app");
    // PyObject *pvalue = Py_BuildValue("l", 3);
    // PyDict_SetItem(pdict, pkey, pvalue);
    // long v = PyLong_AsLong(PyDict_GetItem(pdict, pkey));
    // printf("value: %d\n", v);
    fclose(f);
    PyGILState_Release(gstate);
}

struct Interpreter
{
    Interpreter()
    {
        if (initialized)
            return;
        // initialize Python
        Py_Initialize();
        // initialize thread support
        PyEval_InitThreads();
        tstate = PyEval_SaveThread();
        initialized = true;
    }

    ~Interpreter()
    {
        if (finalized)
            return;
        printf("destructed\n");
        PyEval_RestoreThread(tstate);
        Py_Finalize();
        finalized = true;
    }

private:
    static PyThreadState *tstate;
    static bool initialized;
    static bool finalized;
};
bool Interpreter::initialized = false;
bool Interpreter::finalized = false;
PyThreadState *Interpreter::tstate = NULL;
Interpreter interpreter;

int main()
{
    std::thread thrd(&Test);
    thrd.detach();
    std::thread thrd2(&Test);
    thrd2.detach();
    int i = 10;
    while (i >= 0)
    {
        printf("waiting in main thread, left: %i seconds\n", i);
        sleep(1);
        i--;
    }
}

Makefile

EmbedPyInCpp:EmbedPyInCpp.cpp
	g++ `python3-config --includes` `python3-config --ldflags` $< -o $@

.PHONY:clean
clean:
	rm -rf EmbedPyInCpp

Embed.py

import os
import time
print("current directory is %s" % os.getcwd())
for i in range(10):
    print("current time is %s" % (time.ctime()))
    time.sleep(1)

以上是使用python 的 CAPI,如果使用pybind11的话,参考如下代码:

#include 
#include 
#include 
#include 
#include 
#include "Funcs.h"

namespace py = pybind11;
using namespace py::literals;

void Test(std::shared_ptr math, int i, int j)
{
    py::gil_scoped_acquire acquire;
    auto locals = py::dict("i"_a = i,
                           "j"_a = j,
                           "math"_a = math);
    py::exec(R"(
        print(name)
        try:
            print(sex)
        except Exception as e:
            print(e)
        print('math before change in python:')
        math.show()
        sleep(1)
        math.i = i
        math.j = j
        print('math after change in python:')
        math.show()
        from threading import Thread
        def CountDown(n):
            while n >= 0:
                print("counting down left %i seconds" % n)
                n -= 1
                sleep(1)
        thrd = Thread(target=CountDown, args=(5,))
        thrd.start()
        name = "zen"
        sex = "male"
    )",
             py::globals(), locals);
}

struct PyInterpreter
{
    PyInterpreter()
    {
        if (initialized)
            return;
        py::initialize_interpreter();
        PyEval_InitThreads();
        // enable threading module of python
        // https://stackoverflow.com/questions/27844676/assertionerror-3-x-only-when-calling-py-finalize-with-threads
        Py_DECREF(PyImport_ImportModule("threading"));
        py::exec(R"(
            import sys
            sys.path.append('/home/rvbust/Workspace/Test/EmbedPythonInCpp/Build/Lib')
            from PyFuncs import Math
            from time import sleep
            name = "xiaozhen"
        )",
                 py::globals());
        // release gil and save current thread state
        // https://docs.python.org/3/c-api/init.html#non-python-created-threads
        tstate = PyEval_SaveThread();
        initialized = true;
    }
    ~PyInterpreter()
    {
        if (finalized)
            return;
        // acquire gil and restore current thread state
        PyEval_RestoreThread(tstate);
        py::finalize_interpreter();
        finalized = true;
    }

private:
    static PyThreadState *tstate;
    // initialized and finalized are used to ensure that only one python interpreter is initialized
    static bool initialized;
    static bool finalized;
};
PyThreadState *PyInterpreter::tstate = NULL;
bool PyInterpreter::initialized = false;
bool PyInterpreter::finalized = false;
PyInterpreter g_pyinterpreter;

int main()
{
    auto math = std::make_shared(3, 20);
    printf("math: ");
    math->show();
    Test(math, 0, 0);                    // test in main thread
    std::thread thrd(&Test, math, 1, 2); // test in other thread
    thrd.detach();
    std::thread thrd2(&Test, math, 3, 5); // test in other thread
    thrd2.detach();
    int timeout = 8;
    while (timeout >= 0)
    {
        printf("waiting in main thread left: %i seconds\n", timeout);
        sleep(1);
        timeout--;
    }
    printf("math: ");
    math->show();
    return 0;
}

Funcs.h

#pragma oncce
#include 

class Math : public std::enable_shared_from_this
{
public:
    Math(int i, int j) : i(i), j(j) {}
    void show() { printf("i: %i, j: %i, sum: %i\n", i, j, sum()); }
    int sum() { return i + j; }
    int i{0}, j{0};
};

PyFuncs.cp

#include 
#include 
#include "Funcs.h"

namespace py = pybind11;
using namespace py::literals;

PYBIND11_MODULE(PyFuncs, m)
{
    py::class_>(m, "Math")
        .def(py::init())
        .def_readwrite("i", &Math::i)
        .def_readwrite("j", &Math::j)
        .def("sum", &Math::sum)
        .def("show", &Math::show);
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.12)
project(cmake_example)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Bin)

add_subdirectory(pybind11)
pybind11_add_module(PyFuncs PyFuncs.cpp)

add_executable(TestEmbedPython TestEmbedPython.cpp)
target_link_libraries(TestEmbedPython PRIVATE pybind11::embed pthread)
target_include_directories(TestEmbedPython PRIVATE ${pybind11_INCLUDE_DIR})

 

你可能感兴趣的:(python)