为什么我的socket.makefile对象即使在关闭后也会阻塞?

时间:2011-07-22 20:06:13

标签: python multithreading sockets

如果我使用socket.makefile然后关闭文件对象以及底层套接字,那么对read的后续调用将抛出异常,就像我想要的那样。例如,以下代码按我的预期工作:

import socket
from time import sleep
from threading import Thread

ADDR = ("localhost", 4321)

def listener(sock):
    client,addr = sock.accept()
    sleep(1)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)

f.close()
sock.close()
f.read(8)    # throws an exception, as I'd expect

但是,如果我在文件/套接字仍处于打开状态时调用read,那么该调用将被阻止,然后然后如果我关闭套接字,则read方法仍然没有不回来实际上,它会无限期挂起,直到另一端关闭套接字。以下代码演示了这种令人痛苦的行为:

import socket
from time import sleep
from threading import Thread

ADDR = ("localhost", 4321)

def reader(f):
    print("about to read")
    print("we read %r" % f.read(8))
    print("finished reading")

def listener(sock):
    client, addr = sock.accept()
    sleep(3)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
Thread(target=reader, args=[f]).start()

sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()
sleep(1)
print("we still haven't finished reading!")

对我来说这是一个严重的问题,因为我想在一个线程中对f.read进行阻塞调用,但是仍然可以关闭套接字并让线程从该调用返回(可能通过抛出异常)并退出。但是,所有发生的情况是,只要另一方从不关闭套接字,呼叫就会永久阻塞。

那么Thread1是否有办法在read创建的类似文件的对象上调用socket.makefile,然后让Thread2以一种方式关闭套接字导致Thread1read来电时停止阻止?

编辑:我尝试重新编写程序以完全使用gevent及其套接字和Greenlet方法进行多线程处理,但我的程序仍然完全相同:

from gevent import sleep, socket, spawn

ADDR = ("localhost", 4321)

def reader(f):
    print("about to read")
    print("we read %r" % f.read(8))
    print("finished reading")

def listener(sock):
    client, addr = sock.accept()
    sleep(3)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
spawn(listener, server_sock)

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
spawn(reader, f)

sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()

sleep(1)
print("we still haven't finished reading!")
sleep(2)

我很惊讶地发现即使使用gevent套接字,对read的调用也会在基础套接字关闭后阻止。如果没有办法阻止这种情况,我可能只是接受托马斯的压抑“这是不可能的”答案:(

2 个答案:

答案 0 :(得分:4)

在这种情况下,我使用eventlet取得了成功,而现在,gevent正在做同样的事情:

'猴子修补'套接字库以使用非阻塞I / O.以下是您可以尝试的示例:

>>> from gevent import monkey; monkey.patch_socket()
>>> import socket

并了解这会对结果产生什么影响

答案 1 :(得分:1)

是的,线程有令人痛苦的行为,特别是当同时从多个线程接触套接字之类的东西时。这不是你可以正常工作的东西。没有办法强制一个在读取中被阻塞的线程突破读取或终止 - 从它下面关闭套接字更可能导致你的整个程序出现段错误(或者更糟)想。

处理这种情况的正确方法是使用非阻塞读取。一个体面的事件框架,如Twisted,可以帮助你解决这个问题。