2017-06-18 35 views
1

我希望python執行(類似於subprocess.Popen()?)外部插座連接器後,我有另一個線程在socket.accept()阻止。使一個線程中的「socket.accept」在另一個線程中的某個代碼之前執行? (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() 

基本上,我需要的一切工作就像下面依次是:

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

如果3)2前行),不良後果會怎樣。

沒有線程的解決方案(我認爲它首先肯定,因爲線程是麻煩..)是不可能的,因爲當我不能不打斷subprocess.Popen()接受。

此外,我不想使用time.sleep(SOME_LARGE_VALUE),因爲它也是不可控制的(容易出錯,我使用正確的單詞嗎?),而且速度慢。

我已經瞭解到:Python3(CPython)具有全局解釋器鎖定(GIL)機制。有一次只有一個線程有執行的機會。如果一個線程阻塞(在本例中爲socket.accept()),CPython將轉向另一個線程。 (但是,這並沒有幫助解決問題。)

任何人都知道強制執行命令的pythonic方式(或非pythonic方式)嗎?

+0

「不良後果」?給我們一個提示怎麼樣?我可以看到想要在偵聽之後進行調用,但子進程中接受的地方應該在哪裏運行?在接受之前,在接受之後?爲什麼在子流程運行之前,accept會被阻塞? – tdelaney

+0

一旦'listen(1)'返回,TCP堆棧將在後臺排隊多達1個連接請求,即使你還沒有調用accept。只要你在對方感到無聊並重置連接之前調用accept,它就會完成連接。 – tdelaney

+0

我會試一試......看起來我對套接字系統調用很不熟悉......:P – Thiner

回答

1

listen通知網絡堆棧在後臺開始排隊傳入的連接請求。每次調用accept都會接受隊列中的下一個請求。它看起來像你的子進程想要連接回這個程序。聽完之後就打電話。

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

您已爲您的特定情況提供了有效答案。但是,如果您更喜歡針對更通用的問題使用更通用的解決方案,那麼實現這一點的方法很少。

然後我們來定義一個問題:我想安排一個準備一些資源給工作線程的工作,然後等待主線程中的資源準備就緒。資源的準備只會進行一次。當然,還有一個有效的問題:爲什麼我們不能在一個線程中按順序運行所有的東西?但讓我們認爲這是向多線程世界介紹的練習。

所以,這裏是一些骨架Python代碼:

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() 

當然如預期它不工作,就會有一個ERROR消息某處輸出。但我們可以將我們的問題改爲:什麼代替WAIT HERE

最明顯的,解決這樣的問題會主動等待的最糟糕的方式:

while not data_ready: 
    pass 

嘗試運行這段代碼並觀察CPU使用率(在Linux上使用top)。你會注意到它在等待期間長大。所以,請不要在現實生活中做這樣的事情。

指定資源準備只完成一次。所以,工作線程可以準備數據,然後我們可以等待這個線程在主線程中完成。在這種確定的情況下,這將是我的首選解決方案。

thr.join() 

最後,使用一個完整的鎖和條件變量方案。它需要一些更多的變化,所以一個完整的代碼粘貼在這裏:

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() 

如果您需要以循環方式在一個線程準備的資源(例如,某些數據),而在另一個使用它,這將是一個適當的方法。請查看生產者 - 消費者型號

最後但並非最不重要。我使用global說明符來表示data_ready變量,因爲我很懶,這個例子有些不同。但是,將其視爲一種糟糕的設計。該變量只能在do_sthprepare_sth線程之間共享。你可以玩argskwargs參數到start()方法。

+0

好吧,我認爲一般情況是:當preparer線程就緒時,它本質上被阻塞(因此無法設置變量),並且preparer線程不應該事先設置變量,因爲變量設置和準備好的線程不能被忽略。我應該如何處理這種情況? – Thiner

+0

請問您是否更具體一點,您的期望不適合鎖定和條件變量計劃? – ArturFH

相關問題