我不理解Python中的轮询/选择。

15

我正在使用Python进行一些线程化的异步网络实验,使用的是UDP。

我想了解轮询和select Python模块,我以前没有在C/C++中使用过它们。

它们是做什么的?我有点理解select,但它在监视资源时是否会阻塞?轮询的目的是什么?

3个回答

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设置一组文件描述符,并告诉它在任何fd准备好读取/写入/或发生异常时通知您。

执行(select)时是否会阻塞资源?

是的,或者取决于您传递给它的参数。

正如select man页面所说,它会获取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
如果你使用 read 或者 recv,那么你只能等待一个连接。如果你有多个连接,就必须创建多个进程或线程,这会浪费系统资源。
通过使用 selectpoll 或者 epoll,你可以用一个线程监视多个连接,并在有数据可用时得到通知,然后调用相应连接上的 readrecv 函数。
根据传入的参数,它可能会永久阻塞、阻塞一定时间或者不阻塞。

是的... select() 应该与任何 socket 相关调用 "配合使用",因为 select() 所做的只是告诉进程“阻塞直到其中一个 socket 有可读取的数据、有可写入的缓冲区空间或者直到经过这么多时间” 。select() 返回后你对 sockets 所做的操作不是 select() 所知道或关心的。 - Jeremy Friesner

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() 是否会阻塞都无关紧要,它们不会被调用。

如果一个套接字准备好读取,那么它将有数据,因此它也不会阻塞。


唯一的缺点是您没有提及或链接如何在 data = conn.recv(bufsize, socket.MSG_PEEK) 中获取数据大小,并提到阻塞和非阻塞接收之间的区别。 - Jay-Pi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接