2017-06-18 4 views
1

Ich möchte Python ausführen (ähnlich subprocess.Popen()?) einen externen Socket-Anschluss nachdem ich einen anderen Thread zu blockieren bei socket.accept().Machen Sie "socket.accept" in einem Thread vor einem Code in einem anderen Thread ausführen? (python3)

import socket 
import threading 
import subprocess 

host = '0.0.0.0' 
port = 3333 


def getsock(): 
    server_sock = [] 

    def getsock_server(): 
     sock = socket.socket() 
     sock.bind((host, port)) 
     sock.listen(1) 
     accept_return = sock.accept() # *** CRITICAL ACCEPT *** 
     server_sock.append(accept_return[0]) 
     address = accept_return[1] 
     return address 

    thr = threading.Thread(target=getsock_server) 
    thr.start() 
    """Something that *must* be done after the CRITICAL ACCEPT 
     line is executing and the thread "thr" is blocked. Otherwise 
     the program malfunctions and blows into some undebuggable 
     complexity. ;(
     Although it is a connect operation, it may not be as innocent 
     as belowing lines:   
      client_sock = socket.socket() 
      client_sock.connect((host, port)) 
    """ 
    p = subprocess.Popen(
     ["./connector"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 

    thr.join() 
    return server_sock[0] 


conn, addr = getsock() 

Grundsätzlich muss ich alles wie unten, um arbeiten lassen:

1) thr.start() 
2) sock.accept() 
3) subprocess.Popen() 

Wenn 3) geht vor 2), werden unerwünschte Folge passieren.

Eine Lösung ohne Threads (ich dachte es zuerst definitiv, da Threading mühsam ist ..) ist unmöglich seit ich socket.accept() kann ich nicht einfach subprocess.Popen() ohne die Annahme unterbrechen.

Auch ich möchte nicht time.sleep(SOME_LARGE_VALUE) verwenden, da es auch unkontrollierbar ist (fehleranfällig, verwende ich das richtige Wort?) Und außerdem langsam.

Ich habe gelernt, dass: Python3 (CPython) hat globale Interpreter Lock (GIL) -Mechanik. Zu einem Zeitpunkt hat nur ein Thread die Möglichkeit zur Ausführung. Und wenn ein Thread blockiert (in diesem Fall socket.accept()), wird CPython zu einem anderen Thread wechseln. (Allerdings hilft das nicht mit dem Problem ..)

Jeder weiß eine pythonische Art und Weise (oder eine nicht-so-Python Art) der Durchsetzung der Reihenfolge?

+0

"unerwünschte Folge"? Wie wäre es, uns einen Hinweis zu geben? Ich konnte sehen, dass ich den Anruf nach dem Hören machen wollte, aber wo genau sollte der Subprozess laufen? Kurz vor dem Akzeptieren, gleich nach dem Akzeptieren? Warum ist es wichtig, dass das Akzeptieren blockiert wird, bevor der Subprozess ausgeführt wird? – tdelaney

+0

Sobald "listen (1)" zurückkehrt, wird der TCP-Stack bis zu 1 Verbindungsanforderung im Hintergrund in Warteschlange stellen, auch wenn Sie noch nicht "accept" aufgerufen haben. Solange Sie 'accept' aufrufen, bevor die andere Seite sich langweilt und ihre Verbindung zurücksetzt, wird die Verbindung hergestellt. – tdelaney

+0

Ich werde es versuchen ... Scheint so, als wäre ich mit Socket-Systemaufrufen weit entfernt ...: P – Thiner

Antwort

1

listen weist den Netzwerkstapel an, eingehende Verbindungsanforderungen im Hintergrund in die Warteschlange einzureihen. Jeder Anruf an accept akzeptiert die nächste Anfrage in der Warteschlange. Es sieht so aus, als ob Ihr Subprozess eine Verbindung zu diesem Programm herstellen möchte. Ruf es einfach nach dem Hören an.

import socket 
import threading 
import subprocess 

host = '0.0.0.0' 
port = 3333 


def getsock(): 
    server_sock = [] 
    sock = socket.socket() 
    sock.bind((host, port)) 
    sock.listen(1) 
    p = subprocess.Popen(
     ["./connector"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
    return sock.accept() # *** CRITICAL ACCEPT *** 

conn, addr = getsock() 
1

Sie haben die gültige Antwort für Ihren ganz speziellen Fall erhalten. Wenn Sie jedoch eine allgemeinere Lösung für ein allgemeineres Problem bevorzugen, gibt es nur wenige Möglichkeiten, dies zu erreichen.

Lassen Sie uns dann ein Problem definieren: Ich möchte einen Job zum Vorbereiten einer Ressource auf den Arbeitsthread planen und dann in einem Hauptthread warten, bis die Ressource fertig ist. Die Vorbereitung der Ressource würde nur einmal erfolgen. Natürlich gibt es immer noch eine berechtigte Frage: Warum können wir nicht alle Dinge nacheinander in einem einzigen Thread ausführen? Aber betrachten wir dies als eine Übung, die in die Welt des Multithreading einführt.

So, hier ist einiger Skelett Python-Code:

import threading 
import time 
import random 

data_ready=False 

def do_sth(): 
    global data_ready 

    def prepare_sth(): 
     global data_ready 
     print("preparing, simulated by random wait") 
     time.sleep(random.randrange(5,10)) 
     data_ready=True 
     print("resource is ready") 

    print("do_sth"); 
    thr = threading.Thread(target=prepare_sth) 
    thr.start() 

    # WAIT HERE 

    if data_ready: 
     print("OK") 
    else: 
     print("ERROR") 

do_sth() 

Natürlich ist es funktioniert nicht wie erwartet, und es wird eine ERROR Meldung in der Ausgabe irgendwo sein. Aber wir können unsere Frage zu ändern: Was anstelle von WAIT HERE?

Die naheliegendste und der schlechteste Weg, ein solches Problem aktiv Warte wäre zu lösen:

while not data_ready: 
    pass 

Versuchen Sie diesen Code ausführen und beobachten (mit top unter Linux) CPU-Auslastung. Sie werden bemerken, dass es während des Wartens aufwächst. Also, bitte tu das nicht im wirklichen Leben.

Es wurde festgelegt, dass die Ressourcenvorbereitung nur einmal durchgeführt wird.So kann der Arbeits-Thread die Daten vorbereiten und dann können wir einfach darauf warten, dass dieser Thread im Haupt-Thread endet. Dies würde meine bevorzugte Lösung in einem solchen definierten Fall sein.

thr.join() 

Und schließlich verwendet, um eine vollständige geblasene Sperre und bedingte variable Regelung. Es erforderte einige weitere Änderungen, so dass eine vollständige Code wird hier eingefügt:

import threading 
import time 
import random 

data_ready=False 

def do_sth(): 
    global data_ready 
    lock=threading.Lock() 
    cond=threading.Condition(lock) 

    def prepare_sth(): 
     global data_ready 
     with cond: 
      print("preparing, simulated by random wait") 
      time.sleep(random.randrange(5,10)) 
      data_ready=True 
      print("resource is ready") 
      cond.notify() 

    print("do_sth"); 
    with cond: 
     thr = threading.Thread(target=prepare_sth) 
     thr.start() 
     while not data_ready: 
      print("waiting") 
      cond.wait() 

     if data_ready: 
      print("OK") 
     else: 
      print("ERROR") 

do_sth() 

Wenn Sie benötigen die Ressource vorzubereiten (zum Beispiel einige Daten) in einer zyklischen Weise in einem Thread, und verwenden Sie es in einem anderen, es wäre ein richtiger Ansatz. Bitte suchen Sie nach Producer-Consumer Modell.

Und last but not least. Ich habe einen global Spezifizierer für data_ready Variable verwendet, weil ich faul bin und dieses Beispiel über etwas anderes ist. Betrachten Sie es jedoch als ein schlechtes Design. Die Variable sollte nur zwischen den Threads do_sth und prepare_sth geteilt werden. Sie können mit args und kwargs Parameter zu der start() Methode spielen.

+0

Nun, ich denke, die generische Situation ist: Wenn der Ersteller Thread bereit ist, ist es inhärent blockiert (und somit nicht in der Lage, eine Variable festzulegen), und der Ersteller Thread sollte die Variable nicht im Voraus seit der Lücke zwischen der Variableneinstellung und festlegen Thread, der bereit ist, kann nicht ignoriert werden. Wie soll ich mit diesem Fall umgehen? – Thiner

+0

Könnten Sie bitte genauer sein, wie Ihre Erwartungen nicht in das Lock- und Conditional-Variable-Schema passen? – ArturFH

Verwandte Themen