Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

comment passer une valeur entre threads ?

+5 votes

Besoin : J'ai un programme principal (main) qui fait des actions, par moment il a besoin de faire une requête Http sur un serveur.
Implementation : Pour ne pas blocker le main sur l'attente de connection sachant que le serveur web peut être down, je place la class de requête dans un autre thread. Il recupere l'information (requete http) ou non et doit renvoyer son résultat (un int) au programme principale.
Problème : : D'après la doc python un thread ne peut être lancé qu'une fois (start()) donc j'ai un objet qui crée un thread puis le lance pour faire la requête et ne s'en occupe plus après.

Il me faut un objet (une variable) qui est accessible par le thread et par le main.

Que me conseillez-vous ?

demandé 20-Jan-2015 par turgon37 (148 points)

Quel objet as-tu besoin de rendre accessible ? Type ? Usage ? Contenu ?

J'ai besoin que le thread retourne une valeur (int) au programme principale, mais je ne peux pas utiliser l'instruction return car le main n'est pas en attente sur le thread. Donc il faut que le main déclare un objet qu'il le passe au thread lors de l'init, le thread le renseigne et le main le lit lorsqu'il en a besoin.

Utilises-tu une bibliothèque graphique de type Qt ou Wx ? Car cela pourrait nous orienter !

+1, les libs comme Qt ou Wx ont leurs propres systèmes pour gérer ces problèmes.

nein, mon programme n'est pas graphique c'est un daemon permettant de tester l’accessibilité de mes serveurs

4 Réponses

–1 vote

Quelle bibliothèque utilises-tu?

A priori, de base:

  • ton programme aura accès aux variables globales (depuis partout)
  • tu as possibilité de passer des objects a ton thread, qui pourra les modifiers

Exemple, threads.py:

#! /usr/bin/env python

import time
import thread

_global_variable = {
    'value': None,
}


def run(data):
    time.sleep(1) # to let the init print be done
    _global_variable['value'] = 42
    data['also'] = 'modified'


if __name__ == '__main__':
    by_param_variable = {}
    thread.start_new_thread(run, (by_param_variable, ))
    print(_global_variable)
    print(by_param_variable)
    time.sleep(2) # to let the thread handle its stuffs
    print(_global_variable)
    print(by_param_variable)

A l'exécution, on a bien les valeurs modifiées :

$ ./threads.py 
{'value': None}
{}
{'value': 42}
{'also': 'modified'}
répondu 20-Jan-2015 par ze (308 points)

Avoir des états partagés entre des threads est vraiment une mauvaise idée.

La question parlait d'un object accessible.

Après, suivant les accès souhaités, il est nécessaire de savoir ce que l'on
fait, et utiliser les mécanismes de lock qui vont bien.

Si c'est pour accéder en lecture simple a une valeur simple de facon atomique
(type int, string, ...), ca ne pose pas trop de problème à ma connaissance. Il faut juste bien connaitre son workflow.

Tu parle d'états partagés, moi simplement de zone mémoire/variables accessibles. Pour vraiment parler d'état partagés, il faudrait un workflow plus complet qui se base dessus.

+7 votes

Pour faire ce genre de chose, normalement on créé un worker, et derrière on fait une queue, et on setup une boucle d'événements, etc.

Mais ton cas est simple et il y a une lib pur Python 2 et 3 pour ça :

pip install requests-futures

Et derrière, tu peux faire des requêtes non bloquantes de manière transparente, et juste spécifier un callback pour récupérer le résultat.

import time
from requests_futures.sessions import FuturesSession

session = FuturesSession()

URLs = [
    "http://sametmax.com",
    "http://sebsauvage.net",
    "http://indexerror.net",
    "http://afpy.org",
    "http://0bin.net"
]

def faire_un_truc_avec_le_resultat(future):
    response = future.result()
    # reponse.content contient le corps de la réponse
    print(response.url, response.status_code)

for url in URLs:
    future = session.get(url)
    future.add_done_callback(faire_un_truc_avec_le_resultat)

# Juste pour montrer que c'est bien non bloquant
for x in range(10):
    print(x)
    time.sleep(1)

Et ça sort :

0
1
(u'http://sebsauvage.net/', 200)
(u'http://sametmax.com/', 200)
2
(u'http://indexerror.net/', 200)
(u'http://0bin.net/', 200)
(u'http://www.afpy.org/', 200)
3
4
5
6
7
8
9

Derrière, la lib ouvre un ThreadPool et fait tout le boiler plate dont je parlais. C'est juste une surcouche.

répondu 20-Jan-2015 par Sam (4,984 points)
edité 20-Jan-2015 par Sam
0 votes

Eventuellement, tu peux te servir de Celery. Cet outil te permet de lancer des taches sans bloquer ton processus principal. Exemple vite fait, on installe:
- pip install celery
- pip install requests

fichier tasks.py, contenant les eventuelles taches a executer:

from celery import Celery                                                                                                 
import requests

app = Celery('tasks',
             broker='redis://localhost',
             backend='redis://localhost/0')

@app.task
def reqres():
    return requests.get("http://google.fr/")

lancer le service en tapant ds ton shell:
$ celery -A tasks worker -l info

Dans ton fichier principal test.py:

from tasks import reqres
import time

r = reqres.delay()
print("reqres.delay() ne bloque pas")
time.sleep(3)                                                                                                             
print(r.ready())
print(r.state)
print(r.status)
print(r.successful())
print(r.result)

Dans une autre console, tester en tapant:
$ python test.py

Resultat:

reqres.delay() ne bloque pas
True
SUCCESS
SUCCESS
True
<Response [200]>
répondu 20-Jan-2015 par Nsukami_ (1,998 points)

Ca va rendre la récupération de la valeur de retour au même niveau que l'appel assez compliqué.

@Sam On est d'accord. J'illustrais juste l'aspect "effectuer une tache sans bloquer l'appel principal".

+2 votes

Sans en savoir davantage sur la tâche à effectuer, on peut illustrer aussi une autre approche de communication avec une file d'attente, ça donne parfois des idées :)

import asyncio

q = asyncio.Queue(maxsize=0)

@asyncio.coroutine
def producer():
    for elem in range(5):
        yield from q.put(elem)

@asyncio.coroutine
def consumer():
    while True:
        elems = yield from q.get()
        print(elems)

asyncio.get_event_loop().run_until_complete(producer())
asyncio.get_event_loop().run_until_complete(consumer())
asyncio.get_event_loop().run_forever()
répondu 21-Jan-2015 par DoubleNain (1,718 points)

Ah, un peu d'asyncio, ça fait plaisir.

...