我无法理解Python中的轮询/选择

15 投票
3 回答
17609 浏览
提问于 2025-04-17 02:31

我正在用Python做一些多线程的异步网络实验,使用的是UDP协议。

我想了解一下轮询和Python的select模块,我在C/C++中从来没有用过它们。

这些东西是用来干嘛的呢?我对select有一点了解,但它在监视资源的时候会阻塞吗?轮询的目的是什么呢?

3 个回答

3

select()这个函数会接收三个列表,这些列表里放的是要检查的套接字,分别用来判断三种情况(可读、可写、出错)。然后它会返回一些列表,这些列表通常会比较短,有时候甚至是空的,里面放的是那些真的可以处理的套接字。

s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s1.bind((Local_IP, Port1))
s1.listen(5)

s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2.bind((Local_IP, Port2))
s2.listen(5)

sockets_that_might_be_ready_to_read = [s1,s2]
sockets_that_might_be_ready_to_write_to = [s1,s2]
sockets_that_might_have_errors = [s1,s2]


([ready_to_read], [ready_to_write], [has_errors])  = 
       select.select([sockets_that_might_be_ready_to_read],
                     [sockets_that_might_be_ready_to_write_to], 
                     [sockets_that_might_have_errors],            timeout)


for sock in ready_to_read:
    c,a = sock.accept()
    data = sock.recv(128)
    ...
for sock in ready_to_write:
    #process writes
    ...
for sock in has_errors:
    #process errors

举个例子,如果一个套接字在等待了timeout秒后没有任何连接尝试,那么ready_to_read这个列表就会是空的。这个时候,不管accept()和recv()函数会不会阻塞都没关系,因为它们不会被调用,因为列表是空的……

如果一个套接字准备好可以读取数据,那它肯定有数据可读,所以这时候也不会阻塞。

18

好吧,我们一个问题一个问题来。

这些有什么用?

这里有一个简单的套接字服务器的基本结构:

s_sock = socket.socket()
s_sock.bind()
s_sock.listen()

while True:
    c_sock, c_addr = s_sock.accept()
    process_client_sock(c_sock, c_addr)

服务器会不断循环,接受来自客户端的连接,然后调用它的处理函数与客户端的套接字进行通信。这里有个问题:process_client_sock 可能会花很长时间,甚至可能包含一个循环(这通常是情况)

def process_client_sock(c_sock, c_addr):
    while True:
        receive_or_send_data(c_sock)

在这种情况下,服务器就无法再接受其他连接了。

一个简单的解决办法是使用多进程或多线程,创建一个新的线程来处理请求,而主循环则继续监听新的连接。

s_sock = socket.socket()
s_sock.bind()
s_sock.listen()

while True:
    c_sock, c_addr = s_sock.accept()
    thread = Thread(target=process_client_sock, args=(c_sock, c_addr))
    thread.start()

当然,这样是可行的,但考虑到性能,这并不够好。因为新的进程或线程会占用额外的 CPU 和内存,而服务器可能会有成千上万的连接。

所以 selectpoll 系统调用试图解决这个问题。你给 select 一组文件描述符,并告诉它如果有任何文件描述符准备好进行读/写或发生异常时通知你。

在监视资源时,select 会阻塞吗?

是的,或者说这取决于你传给它的参数。

正如select 手册页所说,它会接收 struct timeval 参数。

int select(int nfds, fd_set *readfds, fd_set *writefds,
       fd_set *exceptfds, struct timeval *timeout);

struct timeval {
long    tv_sec;         /* seconds */
long    tv_usec;        /* microseconds */
};

这里有三种情况:

  1. timeout.tv_sec == 0 且 timeout.tv_usec = 0

    非阻塞,立即返回。

  2. timeout == NULL

    一直阻塞,直到某个文件描述符准备好。

  3. timeout 是正常的

    等待一定时间,如果仍然没有文件描述符可用,就超时并返回。

轮询的目的是什么?

简单来说:轮询在等待 IO 时释放 CPU 进行其他工作

这是基于以下简单事实:

  1. CPU 的速度远远快于 IO
  2. 等待 IO 是浪费时间,因为大部分时间 CPU 会处于空闲状态

希望这对你有帮助。

13

如果你使用 readrecv,那么你只能等待一个连接。如果你有多个连接,就需要创建多个进程或线程,这样会浪费系统资源。

但是使用 selectpollepoll,你可以用一个线程来监控多个连接。当其中任何一个连接有数据时,你会收到通知,然后再对相应的连接调用 readrecv

这个过程可能会一直阻塞,也可能会在设定的时间内阻塞,或者根本不阻塞,这取决于你传入的参数。

撰写回答