[C] segmentation fault en utilisant Python.h

Python API

a marqué ce sujet comme résolu.

Bonjour,

J’essaie de lancer plusieurs fois un code python depuis un code C. La première fois que le code Python se lance, tout fonctionne correctement mais la seconde fois que je tente de le lancer j’obtiens une segmentation fault.

Voici un code qui reproduit le problème:

code C:

`//prgm.c
#include <stdio.h>
#include "Python.h"

int main(void) {
    Py_Initialize();

    /* add . to the path */
    PyObject* sys = PyImport_ImportModule("sys");
    PyObject* path = PyObject_GetAttrString(sys, "path");
    PyList_Insert(path, 0, PyUnicode_FromString("."));

    /* import desired function */
    PyObject* pModule = PyImport_ImportModule("main");
    PyObject* pFunc = PyObject_GetAttrString(pModule, "main");

    /* call it */
    PyObject* pArgs = Py_BuildValue("(s)", "137912500");
    PyObject_CallObject(pFunc, pArgs);
    Py_Finalize();
    fprintf(stderr,"End of python software");
    Py_Initialize();
    /* import desired function */
    pModule = PyImport_ImportModule("main");
    pFunc = PyObject_GetAttrString(pModule, "main");

    /* call it */
    pArgs = Py_BuildValue("(s)", "137912500");
    PyObject_CallObject(pFunc, pArgs);
    Py_Finalize();
    fprintf(stderr,"End of python software");
    while(1);
    return 0;
}`

code Python:

def main(freq):
    print("test python")

Avec le gdb j’ai pu observer que la seconde fois que la ligne pModule = PyImport_ImportModule("main"); s’exécute alors pModule retourne NULL:

`(gdb) next
24	    pModule = PyImport_ImportModule("main");
(gdb) next
25	    pFunc = PyObject_GetAttrString(pModule, "main");
(gdb) print pModule
$4 = (PyObject *) 0x0
(gdb) next

Thread 1 "scheduling" received signal SIGSEGV, Segmentation fault.
0x00007ffff7cadc5e in PyObject_GetAttrString ()
   from /lib/x86_64-linux-gnu/libpython3.8.so.1.0`

En lisant la doc de l’API Python en C, j’ai l’impression que Py_INCREF pourrait résoudre ce problème alors j’ai essayé de le placer après la ligne 15 mais ça n’a pas fonctionné.

J’essaye de lancer un code Python plusieurs fois car à terme j’aimerai utiliser ce code python dans une application multi threads en C que je suis en train d’écrire.

Auriez vous une idée de comment résoudre ce problème ?

Merci d’avance pour votre aide :)

Salut,

Ben… Python va avoir du mal à trouver le module si tu ne mets pas . dans sys.path pour la deuxième fois.

En lisant la doc de l’API Python en C, j’ai l’impression que Py_INCREF pourrait résoudre ce problème alors j’ai essayé de le placer après la ligne 15 mais ça n’a pas fonctionné.

Je ne sais pas trop ce qui t’a laissé pensé ça, mais Py_INCREF est là pour signaler à Python que tu partages l'ownership de ton objet à travers une référence dite forte (par opposition à un emprunt). Ça n’a rien à voir avec le problème présent.

EDIT:

J’essaye de lancer un code Python plusieurs fois car à terme j’aimerai utiliser ce code python dans une application multi threads en C que je suis en train d’écrire.

En général, on fait plutôt l’inverse. On appelle du code C multi-thread depuis Python. Qu’est-ce que tu cherches à faire?

+2 -0

Salut,

Merci pour ton aide, j’ai essayé en rajoutant . dans sys.path, pModule ne retourne plus NULL mais malheureusement il y a toujours une segmentation fault.

Voici le code C que j’ai modifié:

//prgm.c
#include <stdio.h>
#include "Python.h"

int main(void) {
    Py_Initialize();

    /* add . to the path */
    PyObject* sys = PyImport_ImportModule("sys");
    PyObject* path = PyObject_GetAttrString(sys, "path");
    PyList_Insert(path, 0, PyUnicode_FromString("."));

    /* import desired function */
    PyObject* pModule = PyImport_ImportModule("main");
    PyObject* pFunc = PyObject_GetAttrString(pModule, "main");

    /* call it */
    PyObject* pArgs = Py_BuildValue("(s)", "137912500");
    PyObject_CallObject(pFunc, pArgs);
    Py_Finalize();
    fprintf(stderr,"End of python software");
    Py_Initialize();
    
    /* add . to the path */
    sys = PyImport_ImportModule("sys");
    path = PyObject_GetAttrString(sys, "path");
    PyList_Insert(path, 0, PyUnicode_FromString("."));
    
    /* import desired function */
    pModule = PyImport_ImportModule("main");
    pFunc = PyObject_GetAttrString(pModule, "main");

    /* call it */
    pArgs = Py_BuildValue("(s)", "137912500");
    PyObject_CallObject(pFunc, pArgs);
    Py_Finalize();
    fprintf(stderr,"End of python software");
    while(1);
    return 0;
}

Voici ce que le gdb retourne lors de la segmentation fault:

Thread 1 "scheduling" received signal SIGSEGV, Segmentation fault.
__GI___libc_free (mem=0x6e00000079) at malloc.c:3102
3102	malloc.c: Aucun fichier ou dossier de ce type.

Je pensais qu’après Py_Finalize();, la mémoire utilisé par l’interpréteur python était nettoyé mais ça n’en a pas l’air non ?

Sinon je souhaitais juste écrire un programme multi thread en C pour apprendre le multithreading, il n’y a pas de raisons techniques à ce choix. Pour le rendre plus intéressant je me suis lancé dans un projet en lien avec des radios logiciels et la seule librairie simple d’utilisation que j’ai trouvé (gnuradio) ne fonctionnait qu’en python pour les blocs dont j’avais besoin, c’est pour cela que j’utilise l’API python

+0 -0

Je pensais qu’après Py_Finalize();, la mémoire utilisé par l’interpréteur python était nettoyé mais ça n’en a pas l’air non ?

Il suffit de lire la doc pour voir que ce n’est pas garanti, et que dans certains cas ça peut poser problème pour appeler plusieurs fois Py_Initialize et Py_FinalizeEx dans le même programme. Cela dit, ton code ne fait rien de baroque et d’ailleurs il fonctionne chez moi.

Un truc que tu peux vérifier, c’est déjà d’appeler Py_FinalizeEx au lieu de PyFinalize qui renvoie un code d’erreur. Tu peux vérifier si ce code est bien 0.

Tu peux (et devrait, de toute façon) aussi ajouter des tests un peu partout pour vérifier que les pointeurs que tu récupères sont non nuls avant de continuer.

Là comme ça il n’y a rien qui me saute aux yeux qui pourrait être problématique dans ton code (mais bon, c’est du C alors on n’est pas à l’abri de grand chose :-°).

Sinon je souhaitais juste écrire un programme multi thread en C pour apprendre le multithreading, il n’y a pas de raisons techniques à ce choix. Pour le rendre plus intéressant je me suis lancé dans un projet en lien avec des radios logiciels et la seule librairie simple d’utilisation que j’ai trouvé (gnuradio) ne fonctionnait qu’en python pour les blocs dont j’avais besoin, c’est pour cela que j’utilise l’API python

Tu vas avoir un gros problème, c’est que l’API Python n’est pas thread safe. Tu vas avoir besoin de te renseigner sur le GIL et comment le récupérer et relacher. Bref, ça me parait un peu casse-gueule si ton but est de débuter en multi-threading alors que visiblement tu ne connais pas bien l’API Python. Tu vas te battre sur deux fronts à la fois. À ta place, je me familiariserais avec l’API Python et le multithreading indépendamment pour commencer avant de t’attaquer à un projet qui joint les deux.

+2 -0

Salut,

Merci d’avoir pris le temps de tester mon code sur ta machine, je vais essayer avec une version plus récente de python pour voir si ça vient de la (j’utilise la 3.8 actuellement).

Merci aussi pour toutes ces explications sur le GIL, pour l’instant j’utilisais un mutex pour ne jamais avoir deux interpreteurs lancés en même temps mais gérer la solution proposé dans ton lien me parait beaucoup plus propre.

Je viendrai poster la solution à mon problème dès que je l’aurai trouvé :)

Je vais aussi réfléchir à faire tout mon projet en python pour cette fois.

Merci aussi pour toutes ces explications sur le GIL, pour l’instant j’utilisais un mutex pour ne jamais avoir deux interpreteurs lancés en même temps mais gérer la solution proposé dans ton lien me parait beaucoup plus propre.

Avoir deux interpréteurs différents dans deux threads distincts n’est (presque) pas un problème, cf Py_NewInterpreter. Cela dit, l’isolation n’est pas parfaite donc c’est vite casse-gueule selon ce que tu souhaites faire. C’est avoir deux threads qui interagissent avec le même interpréteur qui demande d’acquérir le GIL (et donc effectivement avoir les interactions avec l’interpréteur qui tournent en séquentiel).

Cela dit, encore une fois, c’est pas une façon efficace de paralléliser les opérations (parce qu’en fait tout ce qui va passer par Python sera séquentiel). Tu as plusieurs façons d’organiser les choses pour paralléliser des opérations en passant par Python, et je pense que ça vaut le coup d’éclaircir ça un peu parce que j’ai l’impression que c’est pas très clair pour toi.

  • Tu peux lancer plusieurs processus indépendants qui font chacun tourner un script, c’est la façon de paralléliser la plus bête qui soit, et c’est efficace si chaque processus est effectivement indépendant. Le coût de ça est que tu as plein d’interpréteurs, ça peux être cher en mémoire.
  • Si tu as besoin de faire communiquer tes processus, tu peux chapeauter l’ensemble des processus avec des outils comme MPI (qui a une implémentation Python avec mpi4py), ou encore le module multiprocessing.
  • Si tu as besoin d’un script pour combiner les résultats des processus, tu peux simplement utiliser le module subprocess.
  • Tu peux produire des threads soit depuis Python (avec le module threading) soit depuis un code C qui embarque l’interpréteur, ce que tu essayais de faire mais qui est limité par le GIL. C’est la façon la plus inefficace de procéder puisque le GIL impose que les traitements soient effectivement séquentiels. Si tu passes ton temps dans du code Python, ce sera même plus lent que de tourner en série à cause du coût pour changer de thread et acquérir/rendre le GIL.
  • Tu peux exécuter depuis Python un code C (ou C++, ou Rust, ou autre langage capable de s’interfacer avec l’API Python pour écrire des extensions) qui lui même fait ses traitements sur plusieurs threads, tu ne parallélises pas les opérations en Python, mais tu peux paralléliser les traitements lourds (numpy fait ça par exemple). C’est probablement la façon la plus répandue et efficace d’introduire du parallélisme en Python. Si tu gardes l’interface avec Python la plus fine possible, tu as seulement des coûts avec le GIL/de communication au moment de passer la frontière Python/niveau machine.

Tout ça pour dire que ton projet me parait être une mauvaise idée d’une part comme introduction au multithreading, et d’autre part pour effectivement paralléliser ton application. Pour ce second point, tu devrais fortement réfléchir aux stratégies disponibles et laquelle est adaptée à ton problème (si une solution existe). En tout cas, avoir plusieurs threads qui se battent pour accéder à l’interpréteur n’est que très rarement une solution intéressante.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte