11
votes

Forçant un fil pour bloquer tous les autres threads de l'exécution

mise à jour:

Cette réponse stipule que ce que j'essaie de faire est Impossible d'avril 2013. Cela semble toutefois contredire ce que Alex Martelli a déclaré dans Python Cookbook A > (p. 624, 3ème éd.): p>

À son retour, Pygilstate_ensure () garantit toujours que l'appel Le fil a un accès exclusif à l'interprète Python. C'est vrai Même si le code C appelant est exécuté un fil différent qui est inconnu de l'interprète. P> blockQuote>

Les Docs semblent aussi suggérer que Gil peut être acquis, ce qui me donnerait de l'espoir (sauf que je ne pense pas que je puisse appeler pygilstate_ensure () code> du code python pur, et si je crée une extension C pour l'appeler, je ' Je ne sais pas comment incorporer mon memory_daemon () code> dans cela). P>

Peut-être que je suis peut-être mal interprété par la réponse ou le livre de cuisine Python et les Docs. P>

P> P> P> P> P> P> Question originale: p>

Je veux un thread donné (de filetage code> Module) pour empêcher tout autre thread de fonctionner pendant qu'un certain segment de son code est en cours d'exécution. Quel est le moyen le plus simple de le réaliser? P>

Évidemment, il serait formidable de minimiser les modifications de code dans les autres threads, afin d'éviter d'utiliser des appels C et Direct OS, et de le rendre multiplate-plate-forme pour Windows et Linux. Mais de manière réaliste, je serai heureux de simplement avoir une solution pour mon environnement actuel (voir ci-dessous). P>

Environnement: P>

  • CPPHON LI>
  • python 3.4 (mais peut passer à 3,5 s'il aide) li>
  • Ubuntu 14.04 Li> ul>

    cas d'utilisation: p>

    à des fins de débogage, je calculez la mémoire utilisée par tous les objets (comme indiqué par gc.get_objects () code>) et d'imprimer des Rapport de synthèse à Sys.SsderR Code>. Je fais cela dans un fil séparé, car je souhaite que ce résumé est livré de manière asynchrone à partir d'autres threads; Je mets temps.sleep (10) code> à la fin du tandis que la boucle TRUE code> qui effectue le calcul de l'utilisation de la mémoire réelle. Cependant, le fil de rapport de mémoire prend un certain temps pour compléter chaque rapport et je ne veux pas que tous les autres threads avancent avant la fin du calcul de la mémoire (sinon, l'instantané de mémoire sera vraiment difficile à interpréter). P >

    exemple (pour clarifier la question): P>

    import threading as th
    import time
    
    def report_memory_consumption():
      # go through `gc.get_objects()`, check their size and print a summary
      # takes ~5 min to run
    
    def memory_daemon():
      while True:
        # all other threads should not do anything until this call is complete
        report_memory_consumption()
        # sleep for 10 sec, then update memory summary
        # this sleep is the only time when other threads should be executed
        time.sleep(10)
    
    
    def f1():
      # do something, including calling many other functions
      # takes ~3 min to run
    
    def f2():
      # do something, including calling many other functions
      # takes ~3 min to run
    
    
    def main():
      t_mem = th.Thread(target = memory_daemon)
      t1 = th.Thread(target = f1)
      t2 = th.Thread(target = f2)
      t_mem.start()
      t1.start()
      t2.start()
    
    # requirement: no other thread is running while t_mem is not sleeping
    


1 commentaires

Je crois que Python ne peut exécuter que un fil à la fois à cause de la gil.


4 Réponses :


1
votes

Python est toujours exécutant un fil à la fois en raison de la serrure de l'interprète global. Il ne le fait pas quand multiprocessionnaire est impliqué. Vous pouvez voir Cette réponse pour en savoir plus sur le gil à CPPHON.

note, c'est pseudocode comme je ne le fais pas Savoir comment vous créez des threads / les utiliser / quel code vous exécutez dans des threads. xxx

certainement, il peut être écrit mieux et peut être optimisé.


9 commentaires

Cela peut être le cas, mais cela n'empêche pas le python de libérer la gaine et de changer de filetage pendant la section critique.


Nous devons donc utiliser certains C ou C ++ pour verrouiller / libérer le gil, ce n'est pas ce que nous pouvons faire avec python pur.


@Forcebru hmm .. J'ai demandé le moyen le plus simple de résoudre ce problème, mais s'il n'y a aucun moyen de le faire dans python pur, une solution en C est toujours meilleure que pas de solution du tout! :)


@max, vous pouvez utiliser des Serrures pour le faire. Par exemple, faites une pause des threads pendant qu'une certaine serrure est verrouillée et les faire reprendre leur travail lorsqu'il est libéré.


@Forcebru mais comment? Je ne sais pas où dans les autres threads, l'exécution se produit lorsque l'interprète choisit de passer à l'un d'entre eux, et je ne sais pas comment vérifier un verrou dans chaque ligne de code dans un fil (je pense que c'est impossible?)


Tout d'abord, disant que Gil empêche l'exécution de threads est faux. La déclaration correcte est qu'un seul thread peut exécuter à la fois. Lorsqu'un filetage donne (heure.sleep, serrure.acquire, io appelle), il libère Gil permettant d'exécuter d'autres threads. Également dans le code ci-dessus, utilisez des locaux réentrants pour vérifier à nouveau pour voir si vous avez bien la serrure. N'utilisez pas de variables pour vérifier si le verrouillage est disponible ou non parce qu'il corrompre la valeur.


@Saikiranyerram, vous avez peut-être mal interprété la réponse, comme pour le gil, je dis exactement la même chose vous avez mentionné! Quant au code, toutes les améliorations sont les bienvenues: il a été mentionné que ce code peut être écrit mieux.


J'ai ajouté ma réponse. J'utiliserais des serrures au rente pour vérifier la possession de verrouillage et l'acquisition / la libération dans son contexte ou try-attraper-enfin pour vous assurer que les verrous sont toujours libérés. Je ne sais pas pourquoi temps.sleep car python donnera mais n'exécutera aucun code puisque le thread détient la serrure.


Désolé, je n'étais pas très clair dans ma question. Je l'ai édité pour clarifier. L'utilisation de votre code ne fonctionnera pas pour moi car travailleur () doit être exécuté dans une boucle avec un peu de veille insérée entre les deux, tandis que test () ne doit exécuter que lorsque Travailleur () dort et doit être suspendu autrement. Il semble qu'il faudrait écrire une extension C ou pire.



3
votes

Vous devez utiliser des verrous de filetage pour exécuter du code de manière synchrone entre les threads. La réponse donnée est quelque peu correcte mais j'utiliserais des locaux réentrants pour vérifier à nouveau pour voir si vous avez bien la serrure.

N'utilisez pas de variables comme décrit dans une autre réponse pour vérifier la possession de verrouillage. Les variables peuvent être corrompues entre plusieurs threads. Les serrures réentrantes étaient censées résoudre ce problème.

Aussi ce qui est incorrect dans ce code est que le verrouillage est libéré en supposant que le code entre ne jette pas une exception. Donc, faites toujours dans avec contexte ou try-attraper-catch-enfin .

Voici un excellent Article Expliquer la synchronisation en python et en filetage docs .

EDIT: Répondre à la mise à jour de l'OP sur l'incorporation de Python en C

Vous avez mal compris ce qu'il a dit dans le livre de cuisine. Pygilstate_ensure Retourne le gil si un gil est disponible dans l'interpréteur de python Python actuel mais pas C des threads inconnus de l'interprète Python.

Vous ne pouvez pas forcer à obtenir GIL d'autres threads de l'interprète actuel. Imaginez si vous pouviez, alors fondamentalement, vous serez cannibaliser tous les autres threads.


3 commentaires

Voir mon édition pour clarifier la question. Un simple verrouillage ou RLOCK ne fera pas l'affaire, car l'autre thread doit être arrêté chaque fois que le "fil de contrôle" cesse de dormir, quel que soit l'endroit où le "pointeur d'instruction" "se trouve être dans cet autre fil. (Et bien sûr, je ne peux pas insérer un verrouillage Vérifiez à chaque ligne de code dans l'autre thread.)


Je t'ai eu. Alors, qu'est-ce qui soulève la question de savoir si l'exécution doit suspendre immédiatement ou s'il devrait terminer son unité d'exécution avant de vérifier si elle devrait suspendre ou exécuter. S'il s'agit de ce dernier, il est plus facile en boucle simplement pendant qu'Ipt_run: puis mettez le code à l'intérieur de cette boucle. Vous pouvez ensuite mettre à jour ce drapeau dans un auditeur qui écoute les messages du fil principal sur si vous devriez continuer ou suspendre.


Oui, le tandis qu'Iput_run Construction fonctionnerait pour le code qui permet d'être représenté comme une boucle; Malheureusement, mon code n'est qu'une longue séquence d'opérations. Je devrais essentiellement spraser les chèques de dû_run dans tout le code, en faisant une tâche assez encombrante et crée un cauchemar de maintenance.



1
votes

En tant que solution d'arrêt-gap (pour des raisons évidentes), les suivants ont fonctionné pour moi:

def report_memory_consumption():
  sys.setswitchinterval(1000) # longer than the expected run time
  # go through `gc.get_objects()`, check their size and print a summary
  # takes ~5 min to run
  sys.setswitchinterval(0.005) # the default value


0 commentaires

2
votes

Le livre de recettes Python est correct. Vous avez un accès exclusif à l'interpréteur Python au point où pygilstate_ensure () code> retourne. Accès exclusif signifie que vous pouvez appeler en toute sécurité toutes les fonctions CPPHON. Et cela signifie que le fil C actuel est également le fil de python actif actuel. Si le fil C actuel n'avait pas de fil de python correspondant avant, pygilstate_ensure () code> en aura créé un pour vous automatiquement.

c'est l'état juste après pygilstate_ensure () code >. Et vous avez également le gil gil acquis à ce point. P>

Toutefois, lorsque vous appelez d'autres fonctions CPPHON maintenant, telles que pyeval_evalcode () code> ou tout autre, ils peuvent implicitement faire ce que le Gil est libéré pendant ce temps. Par exemple, c'est le cas si implicitement l'instruction Python temps.sleep (0,1) code> est appelée quelque part en conséquence. Et tandis que le gil est libéré de ce fil, d'autres threads Python peuvent exécuter. P>

Vous n'avez que la garantie que lorsque pyeval_evalcode () code> (ou quelle que soit autre fonction de CPPHON que vous avez appelée) renvoie , vous aurez à nouveau le même état qu'auparavant - c'est-à-dire que vous êtes sur le même fil de python actif et que vous avez à nouveau le gil. P>


À propos de votre question initiale: il n'est actuellement aucun moyen de réaliser cela , c'est-à-dire appeler le code Python et éviter que le gil soit libéré de quelque part quelque part. Et c'est une bonne chose, sinon vous pourriez facilement vous retrouver dans des blocages, par ex. Si vous ne laissez pas d'autre thread de libérer un peu de verrou qui contient actuellement. p>

sur la manière de mettre en œuvre votre cas d'utilisation: le seul moyen réel de le faire est dans C. Vous appelez Pygilstate_ensure () code> pour obtenir le gil. Et à ce stade, vous ne devez appeler que ces fonctions CPPHON qui ne peuvent pas avoir l'effet secondaire d'appeler d'autres codes Python. Soyez très prudent. Même pyobj_decref () code> pourrait appeler __ del __ code>. La meilleure chose à faire serait d'éviter d'appeler des fonctions CPPHON et de traverser manuellement les objets CPPHON. Notez que vous n'avez probablement pas à le faire aussi compliqué que vous l'avez souligné: il y a l'allocator de mémoire CPPHon sous-jacent et je pense que vous pouvez simplement obtenir les informations de là. P>

lire ici sur la gestion de la mémoire en CPPHON. p>

Code associé est dans pymem.h , OBMALLOC.C et pyarena.c . Voir la fonction _pyObject_debugmallocstats () code>, bien que cela ne soit pas compilé dans votre CPPHON. P>

Il y a aussi le module TracemalLoc qui ajoutera quelque chose de surcharge. Peut-être son code C sous-jacent (fichier _tracemalloc.c ) est utile Cependant, pour comprendre les internes un peu mieux. p>


sur SyS.SetswitchInterval (1000) Code>: Cela n'est associé que pour passer par le bytecode Python et le gérer. C'est fondamentalement la boucle principale de cpython dans pyeval_evalfracreex code> dans le fichier ceval.c . Là, vous trouverez une telle partie: p> xxx pré>

toute la logique avec l'intervalle de commutation est couvert dans le fichier ceval_gil.h . p>

Réglage d'un intervalle de commutation élevé signifie juste que la boucle principale dans Pyeval_evalfracreex ne sera pas interrompu pendant plus longtemps. Cela ne signifie pas qu'il n'y a pas d'autres possibilités que le gil pouvait être libéré entre-temps et qu'un autre thread pourrait fonctionner. P>

pyeval_evalframeex code> exécutera le bytecode Python. Supposons que cela appelle temps.sleep (1) code>. Cela appellera la mise en œuvre de la fonction C natif C. Vous constaterez que dans time_sleep () code> dans le fichier Timemodule.c . Si vous suivez ce code, vous trouverez ceci: P>

Py_BEGIN_ALLOW_THREADS
err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
Py_END_ALLOW_THREADS


4 commentaires

Merci, ceci est très utile. Pouvez-vous commenter si ma réponse fonctionne (avec certaines mises en garde)?


@max: J'ai étendu ma réponse.


Merci. Y a-t-il un moyen de collecter les données sur le nombre de commutateurs de threads sur le fait que mon programme fonctionnait?


@max: Non. Vous devriez modifier CPPHON. Mais ce sera difficile. Plus facile serait de mettre en œuvre le comptage de la mémoire en C et ne relâchez-vous pas simplement le gil pendant que vous effectuez le calcul. Je vais étendre ma réponse avec quelques informations supplémentaires à ce sujet.