【问题标题】:Make "socket.accept" in one thread execute before some code in another thread? (python3)让一个线程中的“socket.accept”在另一个线程中的某些代码之前执行? (python3)
【发布时间】:2017-06-18 14:20:42
【问题描述】:

我希望 python 执行(类似于 subprocess.Popen()?)一个外部套接字连接器,因为我在 socket.accept() 有另一个线程要阻塞。

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),就会产生不良后果。

没有线程的解决方案(我首先认为它是肯定的,因为线程很麻烦..)是不可能的,因为当我 socket.accept() 我不能只是 subprocess.Popen() 而不中断接受。

我也不想使用time.sleep(SOME_LARGE_VALUE),因为它也是无法控制的(容易出错,我使用的词是否正确?)而且速度很慢。

我了解到:Python3 (CPython) 具有全局解释器锁定 (GIL) 机制。一次只有一个线程有机会执行。如果一个线程阻塞(在本例中为socket.accept()),CPython 将转向另一个线程。 (但是,这对解决问题没有帮助..)

有人知道执行命令的 Python 方式(或不那么 Python 方式)吗?

【问题讨论】:

  • “不良后果”?给我们一个提示怎么样?我可以看到想要在听后进行调用,但是子进程应该在接受中的哪个位置运行?就在接受之前,就在接受之后?为什么在子进程运行之前接受阻塞很重要?
  • 一旦listen(1) 返回,即使您还没有调用accept,TCP 堆栈也会在后台排队最多1 个连接请求。只要你在对方感到无聊并重置连接之前调用accept,它就会完成连接。
  • 我会试一试...好像我对套接字系统调用很不熟悉...:P
  • 但是,如果发生另一种这样的情况(需要先进行阻塞操作,然后再进行另一个操作),那么 Pythonic/nonpythonic 解决方案是什么?

标签: python multithreading sockets


【解决方案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()

【讨论】:

    【解决方案2】:

    已为您提供了针对您的具体案例的有效答案。但是,如果您希望针对更通用的问题使用更通用的解决方案,则可以通过几种不同的方法来实现。

    然后让我们定义一个问题:我想为工作线程安排准备一些资源的工作,然后在主线程中等待资源准备好。资源的准备将只进行一次。当然,还有一个有效的问题:为什么我们不能在一个线程中按顺序运行所有东西?但让我们将此视为介绍多线程世界的练习。

    所以,这里有一些骨架 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
    

    尝试运行此代码并观察(在 Linux 上使用 top)CPU 使用率。你会注意到它在等待过程中长大。所以,请不要在现实生活中做这样的事情。

    指定资源准备只进行一次。因此,工作线程可以准备数据,然后我们可以在主线程中等待该线程完成。在这种定义的情况下,这将是我的首选解决方案。

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

    如果您需要在一个线程中以循环方式准备资源(例如,一些数据),并在另一个线程中使用它,这将是一种合适的方法。请寻找生产者-消费者模型

    最后但并非最不重要。我对data_ready 变量使用了global 说明符,因为我很懒,这个例子是关于不同的东西。但是,将其视为糟糕的设计。该变量应仅在do_sthprepare_sth 线程之间共享。您可以在start() 方法中使用argskwargs 参数。

    【讨论】:

    • 嗯,我考虑的一般情况是:当准备线程准备好时,它本质上是阻塞的(因此无法设置变量),并且准备线程不应该提前设置变量,因为变量设置和线程就绪之间的差距不容忽视。这种情况应该怎么处理?
    • 能否请您更具体一点,您的期望如何不适合锁定和条件变量方案?
    猜你喜欢
    • 1970-01-01
    • 2015-02-22
    • 2021-06-21
    • 1970-01-01
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多