Python, многопоточность, иногда не удается создать сокеты - PullRequest
3 голосов
/ 18 апреля 2019

Недавно наблюдал довольно странное поведение, которое происходит только в 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,Попытка / исключение вызовет также сбой всех последующих вызовов.
  3. Окружение создания сокета с помощью попытки / исключить и с помощью бита «обработка исключений», перезапускающего функцию, то есть повторный вызов ее будет работать и не потерпит неудачу.

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

PS

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

1 Ответ

1 голос
/ 18 апреля 2019

Мне удалось это решить.Проблема в том, что 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.

...