我正在开发一个带有解释器(用 C++ 编写)的 C++11 程序,它调用各种类方法来完成工作。我想添加使用 Python 3.5 和 matplotlib(后端是 TkAgg)创建数据图的能力。我还希望用户能够在程序中启动 Python 解释器或运行 Python 脚本,以便实时对绘图进行详细调整/增强。
到目前为止,我已经成功地使用 Boost.Python 1.65.1 作为我的接口层创建和保存了绘图,并使用 Python 的代码模块启动了 Python 解释器。但是,matplotlib 绘图的事件循环不会在我的 C++ 解释器提示符下运行(即,如果我在绘图上放置另一个窗口,该图将变为空白并且不会重绘)。只有当用户启动 Python 解释器时,事件循环才会起作用并重新绘制图形。我希望程序表现得好像用户在发出 matplotlib.pyplot.ion() 命令后在本机 Python 解释器中创建了图。我在用于创建绘图的 Python 代码中添加了 plt.ion() 调用,但这似乎对结果行为没有影响。
我试图通过在不同的线程上执行 Python 绘图和 C++ 解释来解决这个问题,但它似乎没有任何帮助。我想知道我是否应该以不同于我目前的方式处理 Python GIL,或者我是否可以做其他事情来让 matplotlib 事件循环在后台线程中运行。我发布了一个简单的示例,重现了我遇到的核心问题。任何帮助表示赞赏。
我尝试过的另一件事是手动调用 matplotlib 的 canvas.start_event_loop()。这似乎可行,但它阻止我创建新的情节,直到它返回/超时,这并不理想。
这是创建绘图的 Python 代码
# Import matplotlib and pyplot and turn on interactive mode
import matplotlib
import matplotlib.pyplot as plt
plt.ion()
def PlotData(num):
""" Create a simple plot and return the figure.
"""
data_list = [[1,2,3,4],[3,2,4,1],[4,3,2,1]]
data = data_list[num]
f = plt.figure()
ax = f.gca()
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.plot([1,2,3,4],data,'bo-')
title = 'Data Set ' + str(num+1)
f.suptitle(title)
print(title)
f.show()
return f
这是C++代码
#include <boost/python.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/chrono.hpp>
#include <iostream>
#include <string>
PyThreadState* mainThreadState = nullptr;
PyThreadState* pts = nullptr; /*!< Pointer for the current thread state */
/*! Initialize Python */
void init()
{
Py_Initialize();
PyEval_InitThreads();
mainThreadState = PyEval_SaveThread();
}
void init_thread()
{
pts = PyThreadState_New(mainThreadState->interp);
}
/*! Calls Python to create a simple plot interactively */
void PlotData(const int& inp)
{
using namespace boost::python;
// Acquire the GIL
std::cout << "Python Thread GIL State (before): " << PyGILState_Check() << std::endl;
PyEval_RestoreThread(pts);
object background = import("sto");
object fig = background.attr("PlotData")(inp);
// The code below will show the plot, but the plot won't update in real time at the C++ command line
object plt = import("matplotlib.pyplot");
fig.attr("show")();
plt.attr("pause")(.1);
std::cout << "Python Thread GIL State (during): " << PyGILState_Check() << std::endl;
// Release the GIL
pts = PyEval_SaveThread();
std::cout << "Python Thread GIL State (after): " << PyGILState_Check() << std::endl;
}
int main(int argc, char* argv[])
{
// Create a thread pool with 1 thread for all Python code
boost::asio::io_service ioService;
boost::thread_group threadpool;
// Start the service
auto work = std::make_shared<boost::asio::io_service::work>(ioService);
// Add a single thread to the thread pool for Python operations
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
// Initialize Python
init();
std::cout << "Submitting init_thread" << std::endl;
ioService.post(boost::bind(init_thread));
// Create some plots on the background thread
std::cout << "Submitting PlotData calls" << std::endl;
ioService.post(boost::bind(PlotData,0));
ioService.post(boost::bind(PlotData,1));
// Pause to allow plots to update in realtime
boost::this_thread::sleep_for(boost::chrono::seconds(4));
// Receive inputs from command line (the plots should update during this time but they don't)
std::string inp{"1"};
std::cout << "Enter to quit\n";
while (!inp.empty())
{
std::cout << ">> ";
std::getline(std::cin,inp);
std::cout << "GIL State: " << PyGILState_Check() << std::endl;
}
// Finalize Python
std::cout << "Submitting Py_Finalize" << std::endl;
ioService.post(boost::bind(PyEval_RestoreThread,pts));
ioService.post(boost::bind(Py_Finalize));
// Allow jobs to complete and shut down the thread pool
work.reset();
threadpool.join_all();
ioService.stop();
return 0;
}