Вызовите несколько Python расширений в c одновременно, используя потоки - PullRequest
0 голосов
/ 16 марта 2020

У меня есть программа c, которая использует 2 потока. Теперь я хотел бы вызвать в обоих потоках функцию Python. Когда я вызываю только одну python функцию в одном потоке, она работает, но при одновременном вызове обеих из них выдает такую ​​ошибку:
Fatal Python ошибка: объект G C уже отслежен.

Осматривая вокруг, я обнаружил, что это вызвано python -функциями и что сборщик мусора не различает оба. Теперь я не могу найти никаких примеров, касающихся этого топи c. Я нашел некоторые подсказки, которые могли бы помочь, но, опять же, нет примеров того, как их реализовать. Я думаю, что мне нужно, чтобы G C знал разницу между ними так же, как mallo c ().

Я думал, что смогу использовать следующую функцию PyObject_GC_New (), но я не знаю, как это реализовать.

Под моим кодом, который работает, когда вы закомментируете audioSend () в speakerPlay () и заменяете его, например, оператором print.

#include <stdio.h>
#include <Python.h>
#include <numpy/ndarrayobject.h>
#include <pthread.h>

typedef struct{
    int length;
    int duration;
}args;

void* micRecord(void* val);
void* calc(void* val);
void* speakerPlay(void* val);

npy_float** audioReceive(int length);
int audioSend(int length);

int main(){
  // init python imports
  Py_Initialize();
  import_array();

  int length = 2;
  int duration = 10;

  args* tArgs = &(args){.length = length, .duration = duration};

  pthread_t recordHandle;
  if( pthread_create(&recordHandle , NULL, micRecord, tArgs))exit( EXIT_FAILURE );

  pthread_t playHandle;
  if( pthread_create(&playHandle , NULL, speakerPlay, tArgs))exit( EXIT_FAILURE );

  void* result;
  if(pthread_join(recordHandle, &result)==-1)
  {
      exit(EXIT_FAILURE);
      printf("could not join\n");
  }

  if(pthread_join(playHandle, &result)==-1)
  {
      exit(EXIT_FAILURE);
      printf("could not join\n");
  }
  printf("Threads joined\n");

  Py_Finalize();
  return 0;
}

void* micRecord(void* rArgs){
  printf("in micRecord\n\n");

  args tArgs = *(args*)rArgs;
  int length = tArgs.length;
  int duration = tArgs.duration;

  npy_float** tempArray;

  for(int i = 0; i<duration; i++){
    tempArray = audioReceive(length);
  }

  printf("micRecord thread done\n");
  pthread_exit(NULL);
}

void* speakerPlay(void* pArgs){
  printf("in speakerPlay\n\n");

  args tArgs = *(args*)pArgs;
  int length = tArgs.length;
  int duration = tArgs.duration;

  for(int i = 0; i < duration; i++){
    audioSend(length);
    //printf("sending\n");
  }

  printf("Speaker thread done\n");
  pthread_exit(NULL);
}

npy_float** audioReceive(int length){
  //variables init
  PyObject *pName, *pModule, *pFunc, *pArgs;
  PyObject* pValue;
  npy_float** array;

  // import the .py file in which the to-call python function is located
  pName = PyUnicode_FromString("receiveSound");
  pModule = PyImport_Import(pName);
  Py_DECREF(pName);
  if(pModule == NULL) printf("its null\n");

  if (pModule != NULL) {
    // Get the reference to the to-call python function and checks if its callable
    pFunc = PyObject_GetAttrString(pModule, "recordSound");
    if (pFunc && PyCallable_Check(pFunc)) {
      // set arguments you want to pass along to the python function
      pArgs = PyTuple_New(1);
      PyTuple_SetItem(pArgs, 0, PyLong_FromLong(length));

      // call the python function and return the numpy array in pValue
      pValue = PyObject_CallObject(pFunc, pArgs);
      if (pValue == NULL) {
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        PyErr_Print();
        fprintf(stderr,"Call failed\n");
        return NULL;
      }

      // get the type description from the array content
      PyArray_Descr *descr;
      descr = PyArray_DescrFromType(PyArray_TYPE(pValue));

      // convert the numpy array to, a by c-compiler useable format, npy_float array
      if (PyArray_AsCArray(&pValue, (void*)&array, PyArray_DIMS(pValue), PyArray_NDIM(pValue), descr) < 0)  {
        PyErr_SetString(PyExc_TypeError, "error converting to c array");
        return NULL;
      }
      //printf("input\n%"NPY_FLOAT_FMT"\n%"NPY_FLOAT_FMT"\n%"NPY_FLOAT_FMT"\n%"NPY_FLOAT_FMT"\n", array[0], array[5], array[10], array[12]);
      Py_DECREF(pValue);
    }
    // if there was no such function in the .py file
    else {
        if (PyErr_Occurred())
            PyErr_Print();
        fprintf(stderr, "Cannot find function \n");
    }
    Py_XDECREF(pFunc);
    Py_DECREF(pModule);
  }

  // if there was no such .py file
  else {
    PyErr_Print();
    fprintf(stderr, "Failed to load \\n");
    return NULL;
  }

  return array;
}

int audioSend(int length){
  // init variables
  PyObject *pName, *pModule, *pFunc;
  PyObject *pValue;

  // import the .py file in which the to-call python function is located
  pName = PyUnicode_FromString("ss");
  pModule = PyImport_Import(pName);
  Py_DECREF(pName);
  if(pModule == NULL) printf("its null\n");

  if (pModule != NULL) {
      // Get the reference to the to-call python function and checks if its callable
      pFunc = PyObject_GetAttrString(pModule, "playSound");
      if (pFunc && PyCallable_Check(pFunc)) {

        // call the python function to play the sound by reading the array
        pValue = PyObject_CallObject(pFunc, NULL);

        // check if array is played
        if (pValue != NULL) {
            Py_DECREF(pValue);
        }

        // if the array was not correctly read
        else {
            Py_DECREF(pFunc);
            Py_DECREF(pModule);
            PyErr_Print();
            fprintf(stderr,"Call failed\n");
            return 1;
        }
      }

      // if no such function exists in the .py file
      else {
          if (PyErr_Occurred())
              PyErr_Print();
          fprintf(stderr, "Cannot find function \n");
      }
      Py_XDECREF(pFunc);
      Py_DECREF(pModule);
    }
    // if no such .py file exists
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \\n");
        return 1;
    }
  return 0;
}

Вот два python кодовых файла, это действительно базовые c. Они содержатся на моем устройстве в файлах receiveSound.py и ss.py

receiveSound.py:

import numpy as np
from array import array

def recordSound(length=50):

    print ("-----------------RECORDING SOUND------------------")

    # Make the array `rec_array`
    recArray = np.array([[0,1,2,3,4], [5,6,7,8,9]], dtype=np.float)

    return recArray

ss.py

def playSound():

    print ("----------------SOUND IS PLAYED------------------")#recArray[49]
    return 1

и вот мой make-файл, где basicAN C. c является моим c -кодом

basicANCmake: basicANC.c
    gcc -o basicANC -I/usr/include/python3.6m basicANC.c -lpython3.6m -lpthread
run: basicANCmake
    PYTHONPATH=. ./basicANC

1 Ответ

0 голосов
/ 17 марта 2020

Python защищает пространство своего объекта от одновременного доступа через глобальную блокировку, соответственно называемую «Глобальной блокировкой интерпретатора». Вы должны уважать и учитывать это, чтобы использовать один и тот же встроенный интерпретатор Python из нескольких потоков программы хоста C. Документация содержит раздел, описывающий эти требования . Есть способы получить детальный контроль над всем этим, но, согласно документам,

[t] он типичная идиома для вызова Python из потока C: 1006 *

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);

Я предлагаю включить этот шаблон в вашу программу, обновив функции micRecord() и speakerPlay() для переноса каждого вызова на audioReceive() и audioSend() между PyGILState_Ensure() и a PyGILState_Release().

Обновление:

Дополнительно

  • В зависимости от того, какая версия Python вы используете, вам может потребоваться вызвать PyEval_InitThreads(), прежде чем использовать какую-либо функцию обработки потока и GIL. Вы должны сделать это после вызова Py_Initialize() в том же потоке. Это безопасно делать даже на Python версиях, которые на самом деле этого не требуют.

  • Вероятно, вам нужен основной поток, чтобы освободить GIL, прежде чем другие потоки смогут его получить. Вызов функции PyGILState_Check() в главном потоке подтвердит (или опровергнет) это. Есть несколько способов освободить GIL, но, возможно, проще всего заключить в скобку раздел main(), который создает и затем соединяет дочерние потоки между Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS. Прочтите документы по этим макросам, чтобы узнать о них и о том, как их использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...