Python,多线程,套接字有时创建失败

Python, Multithreading, sockets sometimes fail to create

最近观察到一个相当奇怪的行为,它只发生在 Linux 而不是 freeBSD 并且想知道是否有人有解释或至少猜测可能真正发生的事情。

问题:

套接字创建方法 socket.socket() 有时会失败。这仅在多个线程创建套接字时发生,单线程工作正常。

扩展 socket.socket() 失败,大多数时候我收到“错误 13:权限被拒绝”,但我也看到了“错误 93:协议不受支持”。

备注:

  1. 我已经在 Ubuntu 18.04(有 bug)和 freeBSD 12.0(没有 bug)上试过了
  2. 只有在多个线程创建套接字时才会发生
  3. 我使用 UDP 作为套接字协议,尽管它似乎更容错。我也用 TCP 试过了,它甚至会因为类似的错误而变得更快。
  4. 它只是有时会发生,因此可能需要多个 运行,或者就像我在下面提供的情况一样 - 臃肿的线程数也应该可以解决问题。

代码:

这里有一些您可以用来重现的最小代码:


from threading import Thread
import socket

def foo():
    udp = socket.getprotobyname('udp')
    
    try:
        send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, udp)
    except Exception as e:
        print type(e)
        print repr(e)
    

def main():
    for _ in range(6000):
        t = Thread(target=foo)
        t.start()

main()

注:

  1. 我人为地使用了大量线程,只是为了最大限度地提高您在使用 UDP 的 运行 中至少遇到一次该错误的可能性。正如我之前所说,如果您尝试使用 TCP,您会看到大量的线程错误。但实际上,即使是 20 甚至 10 个更真实的线程数量也会触发错误,您可能需要多个 运行s 才能观察到它。
  2. 用 while 围绕套接字创建,try/except 将导致所有后续调用也失败。
  3. 用 try/except 围绕套接字创建并在“异常处理”位中重新启动该函数,即再次调用它会工作并且不会失败。

欢迎任何想法、建议或解释!!!

P.S.

从技术上讲,我知道我可以通过让单个线程根据需要创建尽可能多的套接字并将它们作为参数传递给我的其他线程来解决我的问题,但这不是真正的重点。我更感兴趣的是为什么会发生这种情况以及如何解决它,而不是可能有什么解决方法,尽管这些也很受欢迎。 :)

我设法解决了。问题来自 getprotobyname() 不是线程安全的!

见: The Linux man page

另一方面,看freeBSD man page也暗示这可能会导致并发问题,但是我的实验证明不会,也许有人可以跟进?

无论如何,对于任何感兴趣的人来说,固定版本的代码是在主线程中获取协议号(看起来很明智并且应该首先这样做)然后将其作为参数传递。它既可以减少您执行的系统调用,也可以修复程序中与并发相关的任何问题。代码如下所示:

from threading import Thread
import socket

def foo(proto_num):
    try:
        send_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, proto_num)
    except Exception as e:
        print type(e)
        print repr(e)


def main():
    proto_num = socket.getprotobyname('udp')
    for _ in range(6000):
        t = Thread(target=foo, args=(proto_num,))
        t.start()

main()

以"Permission denied" 或"Protocol not supported" 形式创建套接字的异常不会以这种方式报告。另外,请注意,如果您使用 SOCK_DGRAM,则 proto_number 是多余的,可能会被完全跳过,但是如果有人想要创建一个 SOCK_RAW 套接字,该解决方案会更相关。