在python协同程序中,插座.插座()可能返回占用的文件描述符

2024-06-28 20:38:36 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一段python代码来练习python协同例程。 如A. Jesse Jiryu Davis所解释。在

  • 首先,我定义了一个名为“get”的协同例程来获取一些 网址。在
  • 然后我定义了一个任务类来迭代co例程以完成。在
  • 然后我创建两个任务打开两个不同的URL。在

但我收到了错误信息: KeyError:“368(FD 368)已在行中注册”选择器寄存器(s.fileno(),EVENT_WRITE)。在

此错误是由以下两个调用返回的相同文件描述符引起的插座.插座(). 实际上,这个文件描述符368已经在前一次调用中被分配,但在第二次调用中仍然返回。在

  • 然后添加一个表达式来修改外部变量。在

这一次错误信息消失了! 如果你想自己运行代码,你可以取消注释附件(self.init)在任务.步骤方法查看无错误输出。在

EDIT如果我显式调用python垃圾回收,偶尔这个错误就会消失。为什么偶尔呢?

在搜索和阅读python文档几天后,我仍然不知道为什么会发生这种情况。我错过了一些“Python陷阱”,是吗?在

我使用python3.6进行测试。代码如下,我删除了所有不相关的代码,以使下面的代码更加精确并与主题相关:

#! /usr/bin/python

from selectors import DefaultSelector, EVENT_WRITE
import socket
import gc

selector = DefaultSelector()
arr = [1, 2, 3]

class Task:

    def __init__(self, gen):
        self.gen = gen
        self.step()

    def step(self):
        next(self.gen)
        # arr.append(self.__init__)

def get(path, count = 0):
    s = socket.socket()
    print(count, 'fileno:', s.fileno())
    s.connect(('www.baidu.com', 80))
    selector.register(s.fileno(), EVENT_WRITE)
    yield

Task(get('/foo',1))
gc.collect()
Task(get('/bar',2))

Tags: 代码importselfeventtaskgetinitdef
2条回答

@何艾伦,非常感谢你的回复。在

  1. 进一步研究,得出结论:问题不是由gc引起的。在

The "garbage collector" referred to in gc is only used for resolving circular references. In Python (at least in the main C implementation, CPython) the main method of memory management is reference counting. In my code, the result of Task() has no references, so will always be disposed immediately. There's no way of preventing that no matter you use gc.disable() or anything else.

来自@Daniel Roseman的配额,请参见:

how to prevent Python garbage collection for anonymous objects?

  1. 另外,pythonggc不是线程。相反,它是同步的

请参见:Why python doesn't have Garbage Collector thread?

  1. 所以我的问题的最终答案是:

,插座.插座()不会返回已占用的文件描述符。如果一个fd已经被释放,这意味着一个fd已经被释放了。在

注:

  1. 您的get('/foo', 1)是匿名生成器对象,Task(get('/foo', 1))是匿名Task对象。在
  2. GC是python垃圾收集/收集器的缩写

原始代码的引用链是:

selector  > # socket_fd
anonymous Task(get('/foo', 1))  > anonymous get('/foo', 1)  > s

因此,匿名Task(get('/foo', 1))对象一完成就被GC收集。这是因为:

Python GC will collect the memory of an object as soon as it finds the object's reference count == 0. But python GC is not running as a thread so maybe not the moment right after the object's reference count decreases to 0.

那么匿名的get('/foo', 1)将被收集,然后是{}。在这里,s被收集、关闭,其对应的套接字fd编号(在您的示例中是#368)已被释放。在

但是套接字fd号(#368)已注册到selector。在

然后运行Task(get('/bar',2)),一个新的socket s试图申请一个“新”fd,因为#368是可用的(只要系统中的其他进程没有声明它),您将得到#368作为套接字fd。在

在中取消注释arr.append(self.__init__)任务.步骤()

在中取消注释arr.append(self.__init__)后任务.步骤()方法,全局arr包含对Task(get('/foo', 1))的引用。那么Task(get('/foo', 1))引用了get('/foo', 1)。然后get('/foo', 1)引用了本地套接字s。该参考链如下:

^{pr2}$

arr通过您的程序是有效的,因此s将不会被GC收集。后面的s = socket.socket()将不会得到相同的fd,因为它仍然由{}持有。在

使用s代替s.fileno()

如果使用selector.register(s ..)而不是selector.register(s.fileno()..),全局selector将保存对本地s的引用,引用链是:

selector  > s

虽然这两个匿名对象已经不存在,但是您的get('/foo', 1))::s和{}仍然由全局selector持有。所以不要担心这两个fd不会碰撞。在

循环参考?在

答案是否定的。你的情况与循环引用无关。在

在收集gc?在

好吧,用time.sleep(0.02)代替它,你会观察到同样的现象。这可能是由于:

  1. 套接字来了,套接字去了,由系统的其他进程驱动。在
  2. python GC线程可能需要一些时间,直到它“找到”应该被收集的s,或者线程正在收集。在

相关问题 更多 >