Python, многопоточность, сокеты иногда не удается создать

#linux #python-2.7 #sockets #python-multithreading #low-level

#linux #python-2.7 #сокеты #python-многопоточность #низкоуровневый

Вопрос:

Недавно наблюдал довольно странное поведение, которое происходит только в Linux, но не во FreeBSD, и мне было интересно, есть ли у кого-нибудь объяснение или хотя бы предположение о том, что на самом деле может происходить.

Проблема:

Метод создания сокета, socket.socket() , иногда дает сбой. Это происходит только тогда, когда сокеты создаются несколькими потоками, однопоточный работает просто отлично.

В дополнение к socket.socket() сбоям, большую часть времени я получаю «ошибка 13: отказано в разрешении», но я также видел «ошибка 93: протокол не поддерживается».

Примечания:

  1. Я пробовал это на Ubuntu 18.04 (ошибка есть) и FreeBSD 12.0 (ошибка отсутствует)
  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, вызовет ошибку, вам, вероятно, потребуется несколько запусков, чтобы ее заметить.
  2. Окружение создания сокета с помощью while , try/except приведет к сбою всех последующих вызовов.
  3. Окружение создания сокета с помощью try / except и в бите «передача исключений» перезапуск функции, т. Е. Повторный вызов будет работать и не приведет к сбою.

Любые идеи, предложения или объяснения приветствуются!!!

PS

Технически я знаю, что могу обойти свою проблему, если один поток создаст столько сокетов, сколько мне нужно, и передаст их в качестве аргументов другим моим потокам, но на самом деле это не главное. Меня больше интересует, почему это происходит и как это решить, а не какие могут быть обходные пути, хотя они также приветствуются. 🙂

Ответ №1:

Мне удалось решить эту проблему. Проблема getprotobyname() заключается в том, что она не является потокобезопасной!

См.: Справочная страница Linux

С другой стороны, просмотр справочной страницы FreeBSD также намекает на то, что это может вызвать проблемы с параллелизмом, однако мои эксперименты доказывают, что это не так, может быть, кто-нибудь может продолжить?

В любом случае, фиксированная версия кода для всех заинтересованных лиц состояла бы в том, чтобы получить номер протокола в основном потоке (кажется разумным и следовало бы сделать это в первую очередь), а затем передать его в качестве аргумента. Это позволит как сократить количество выполняемых вами системных вызовов, так и устранить любые проблемы, связанные с параллелизмом, связанные с этим в программе. Код будет выглядеть следующим образом:

 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()

 

Исключения с созданием сокета в виде «Отказано в разрешении» или «Протокол не поддерживается» не будут сообщаться таким образом. Также обратите внимание, что если вы используете SOCK_DGRAM, proto_number является избыточным и может быть вообще пропущен, однако решение было бы более уместным в случае, если кто-то захочет создать сокет SOCK_RAW.