在C ++中从多个线程调用Python函数

时间:2019-03-01 11:56:15

标签: python c++ multithreading

找到了几个类似的线程,但没有任何帮助。

基本上,我有一个C ++应用程序,想从Python脚本中调用一个函数。这一切都很好。

但是因为我需要它实时地工作并且python函数需要一些时间,所以我想添加多线程。

基本上是两种情况之一:

  1. C ++中的线程池,每个线程将在其中调用Python函数。

  1. Python中的线程池,C ++应用程序正在将任务添加到Python队列中。

我有点喜欢第一种选择,所以让我们开始吧。基本上,我有一个带功能的python脚本(实际上,有一个Tensorflow预测):

import time
def pfoo(msg):
  print("Python >> Function called)
  time.sleep(2)
  print("Python << Function finished)

还有一个C ++线程池(主要取自here):

ThreadPool pool(4);

// initialize the Python
Py_Initialize();
// initialize thread support
PyEval_InitThreads();

// ...

PyObject* m_PyModule = PyImport_ImportModule( "test" );

PyObject* m_PyDict = PyModule_GetDict( m_PyModule );

PyObject* m_PyFoo = PyDict_GetItemString( m_PyDict, "pfoo" );

for ( int i = 0; i < 10; i++ ) { 

    pool.enqueue( [&] { 

      PyEval_CallObject( m_PyFoo, Py_BuildValue( "(s)", arg ) );

    } );
}

您可以想象,什么也没有发生,因为您不能只在运行时调用同一函数。 我尝试了Py_BEGIN_ALLOW_THREADS宏,我尝试了PyGILState_Ensure()事物和this更复杂的选项。我没主意了。

A尝试了第二种情况,我在Python中有一个无限循环线程,该线程从queue.Queue()读取任务并将其放入ThreadPoolExecutor,然后C ++应用程序调用一个函数以添加一个任务放入上述队列中。也不对我有用(如果我只是在Python中运行它就可以,但是如果嵌入到C ++中就不起作用)。

1 个答案:

答案 0 :(得分:0)

我想我可能已经找到了可行的解决方案。适用于综合示例,但需要使用Tensorflow预测进行实时测试:

python脚本仍然使用线程池,但没有无限的线程循环:

from concurrent.futures import ThreadPoolExecutor
import threading
import time

executor = ThreadPoolExecutor(max_workers=4)
lock = threading.Lock()
counter = 0

def worker(msg, n):
  global counter

  with lock:
    counter += 1

  print("Python >> Function called with (%s, %d)" % (msg, n))

  for i in range(n): # do some work...
    print("\tPython :: %s: %d" % (msg, i))
    time.sleep(1)

  print("Python << Finished function for (%s, %d)" % (msg, n))

  with lock:
    counter -= 1
    if counter < 0:
      counter = 0


def add(msg, n):
  global executor, counter
  if counter < 4:
    print("++%d threads are free, adding task: %s" % (4 - counter, msg))
    executor.submit(worker, msg, n)

C ++应用程序仅在必要时调用“添加”功能:

std::map<std::string, PyObject*> m_PyFunctions;

m_PyFunctions["add"] = PyDict_GetItemString( m_PyDict, "add" );

for ( int i = 0; i < 100; i++ ) 
{
    std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );

    PyEval_CallObject( m_PyFunctions["add"], 
                       Py_BuildValue( "(s, l)", "task_#" + std::to_string( i ), std::rand() % 5 ) );
}

编辑:是的,使用我的主应用程序对此进行了测试,它工作正常。粗略的解决方案,但是仍然可以通过。