soucis de programmation concurrente

Le problème exposé dans ce sujet a été résolu.

Bonjour amis agrumes,

j'écris un script qui doit permettre de relever certains indicateurs réseaux sur un lien en utilisant l'outil iperf3. Actuellement il y a deux machines que je vais noter A et B. B avec l'adresse ip 192.168.1.138 joue le rôle de serveur et A avec l'ip 192.168.1.91 joue le rôle de client.

Sur le serveur j'ai un script bash qui ouvre mon logiciel iperf sur un ensemble de serveurs

1
2
3
4
for j in `seq 5000 5020`
do
    /home/pi/rpi_scripts/iperf3 -s -i 1 -p $j
done

Ensuite sur mon ordinateur qui sert de client j'ai crée un un thread qui exécute cette méthode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def iperf(self):
    """Brief: effectue des mesures sur la sonde ciblée en testant un
    ensemble de ports et renvoie la gigue et le taux de perte"""
    # Variable qui va permettre d'arrêter le parcours des ports dès
    # que l'un d'entre eux aura permis de faire les mesures
    ok = False
    with self.lock:
        for port in range(self.start_port, self.end_port + 1):
            iperf_command = os.popen('iperf3 -c {} -u -b {} -p {} -t {}'.
                                     format(self.ip, BANDWIDTH, port, DURATION))
            result = iperf_command.read()
            # S'il y a une erreur dans l'exécution de la commande, on passe
            # l'itération suivante
            if result.find('error') != -1:
                continue
            # S'il n'y a pas d'erreur, on analyse le résultat
            result_lines = result.split('\n')
            for line in result_lines:
                if re.search(r'/sec  (.*) ms  (\d+)/(\d+) (.*)', line):
                    kpi_search = re.search(r'/sec  (.*) ms  (\d+)/(\d+) \((.*)%\)', line)
                    jitter = kpi_search.group(1)
                    packet_loss = kpi_search.group(4)
                    ok = True
            # Si on a pu mesurer les indicateurs, il est inutile de parcourir
            # les autres ports
            if ok:
                break
    # Si les mesures se sont bien passés, on retourne les indicateurs
    if ok:
        return jitter, packet_loss
    # Sinon on retourne un message d'erreur
    else:
        return "Impossible de mesurer les indicateurs gigue et taux " \
               "de perte sur l'adresse ip {}".format(self.ip)

Ensuite je fais des tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if __name__ == '__main__':
    thread1 = Measure('192.168.1.138', 5000, 5020)
    thread2 = Measure('192.168.1.138', 5000, 5020)
    thread3 = Measure('192.168.1.138', 5000, 5020)
    # on lance les thread
    thread1.start()
    thread2.start()
    thread3.start()
    # On attend qu'ils se terminent ensemble
    thread1.join()
    thread2.join()
    thread3.join()

Et j'obtiens le résultat suivant

1
2
3
4
5
6
7
8
Thread-2
latency:  3.887
error:  Impossible de mesurer les indicateurs gigue et taux de perte sur l'adresse ip 192.168.1.138
Thread-3
latency:  4.618
error:  Impossible de mesurer les indicateurs gigue et taux de perte sur l'adresse ip 192.168.1.138
Thread-1
pl:  0  latence:  3.298  gigue:  7.516

En gros, le résultat ne marche que sur un thread, pourtant j'ai bien utilisé un verrou pour éviter d'éventuels soucis de concurrence. A noter que j'ai une autre méthode, le ping qui s'exécute normalement (et me renvoie la latence) sans même que j'ai à utiliser un verrou. Peut-être le problème ne viendrait pas de python mais du logiciel en lui-même?

Bref je suis ouvert à toute suggestion :)

Je ne sais pas ce qui justifie ton verrou en fait. En principe, poser un verrou ne devrait pas se faire au petit bonheur la chance mais juste pour protéger l'accès à une ressource particulière, en verrouillant la partie de code la plus petite possible contenant toutes les opérations à protéger.

Dans le cas présent, c'est l'exécution concurrente de iperf3 qui semble crasher, mais le lock (qui pourtant englobe un code encore plus large que ça) n'opère visiblement pas comme prévu.

Au vu de (ce que tu nous montres de) ton code j'ai l'impression que tu as une instance du lock différente par thread, ce qui ne sert à rien : si tu veux utiliser un lock, il faut que ce soit le même, partagé par tous les threads… mais avant de corriger le code du lock, il faut se demander ce qui prend le plus de temps dans cette boucle. Si le code à l'intérieur du lock prend plus de 90% du temps du thread, alors n'utilise pas de thread, ça n'est pas rentable et ça rajoute de la complexité pour rien.

PS : en fait étant donné que ton programme ne sert qu'à piloter des appels à subprocess, tu n'as même pas du tout besoin de threads. Tu peux tout à fait lancer un subprocess et l'attendre de façon asynchrone.

+0 -0

Merci pour les explications, par contre

Si le code à l'intérieur du lock prend plus de 90% du temps du thread, alors n'utilise pas de thread, ça n'est pas rentable et ça rajoute de la complexité pour rien.

PS : en fait étant donné que ton programme ne sert qu'à piloter des appels à subprocess, tu n'as même pas du tout besoin de threads. Tu peux tout à fait lancer un subprocess et l'attendre de façon asynchrone.

Je n'ai pas compris ce que tu veux dire.

Edit: il faut savoir que j'utilise ce script dans un autre où j'exécute des threads sur plusieurs adresses ip. C'est d'ailleurs pour ça que j'ai pensé aux threads, je me suis dit que ça irait plus vite si le client pouvait mesurer simultanément différents hôtes. Ai-je tord?

+0 -0

En fait ça dépend de la nature du boulot que doivent faire tes threads et en particulier si le travail est parallélisable.

En l'occurrence ici on dirait que iperf3 ne supporte pas les exécutions concurrentes, donc tu es obligé de poser un lock sur son exécution, de façon à ce que deux threads ne l'exécutent pas en parallèle. Attention je dis ça sans certitude sur le fait que tu puisses ou pas l'exécuter en parallèle hein.

Maintenant si exécuter iperf3 est ce qui prend le plus clair du temps que met ton thread à s'exécuter (ce qui est très probable), ça ne te fait plus rien gagner d'utiliser des threads : en lançant la fonction de façon séquentielle, tu n'auras pas des perfs différentes, mais le code sera plus simple (la règle d'or, penser de façon concurrente, c'est difficile, même quand ça n'en a pas l'air).

À ta place, je commencerais par déterminer si c'est vraiment le fait d'executer plusieurs instances de iperf3 en parallèle le problème.

Si oui, je récrirais le code de façon purement séquentielle sans thread. Sinon, j'utiliserais les objets Popen de façon asynchrone (tu peux lancer un processus avec Popen sans bloquer et récupérer le résultat plus tard avec la méthode wait et en lisant son attribut stdout), au lieu de les lancer dans des threads.

+0 -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