Почему (Py) ZeroMQ открывает так много файлов сокетов Unix?

Я пытался отслеживать количество открытых файлов сокетов Unix с помощью lsof -U | wc -l lsof -U | wc -l пока я выполняю этот код:

>>> import zmq
# 1375 Unix socket files
>>> c = zmq.Context()
# 1377 Unix socket files
>>> s = c.socket(zmq.PUSH)
# 1383 Unix socket files
>>> s.close()
# 1381 Unix socket files
>>> c.destroy()
# 1375 Unix socket files

Это почему? Я ожидал, что при подключении сокета будет открыт файл сокета TCP/IPC, но как быть с этими файлами еще до подключения?

Кажется, они все типа "STREAM":

enter image description here

Обновить

Посмотрите ответ @gdlmx, чтобы найти полезный скрипт для воспроизведения этой проблемы.

Кажется, если вы используете Conda для установки pyzmq все работает как положено. Мне, однако, все еще интересно знать, почему это не будет работать, если вы установите pyzmq с pip, который я бы посчитал стандартным способом установки пакета.

Действия по воспроизведению:

С Кондой:

conda create -n foo python=3.6
conda activate foo
pip install pyzmq
python test_script.py

С Python venv:

python3.6 -m venv venv
source ./venv/bin/activate
pip install pyzmq
python test_script.py

Ответ 1

Я рекомендую перезапустить ваш тест с простым python или ipython (без консоли). Также ограничьте счет одним процессом с помощью lsof -p <pid> чтобы исключить ненужные помехи от других процессов на вашем компьютере (эти 1375 файлов сокетов Unix в вашем тесте).

Вот простой тестовый скрипт:

import os
pid = os.getpid()
count=0

def lsof():
    global count
    count += 1
    print(count,':')
    os.system("lsof -p {0:d} 2>/dev/null | grep -E 'unix|IPv4|IPv6'".format(pid)) # -U doesn't work togeter with -p option
    # Alternatively, you can use "lsof -U 2>/dev/null | grep -E {0:d}"
    # but only unix socket file will be listed.

import zmq
c = zmq.Context();lsof()
tcp = c.socket(zmq.PUSH);lsof()
unix = c.socket(zmq.PUSH);lsof()

print('--- To bind  ---')
tcp.bind('tcp://127.0.0.1:19413');lsof()
unix.bind('ipc://filename');lsof()

print('--- To close ---')
tcp.close();lsof()
unix.close();lsof()

Ниже приведен результат теста в моей среде (python 3.6.6, pyzmq 17.1.2, с Anaconda в CentOS 7).

1 :
2 :
3 :
--- To bind  ---
4 :
ZMQbg/1 284018 gdlmx   13u     IPv4           49443178      0t0      TCP localhost:19413 (LISTEN)
5 :
ZMQbg/1 284018 gdlmx   13u     IPv4           49443178      0t0      TCP localhost:19413 (LISTEN)
ZMQbg/1 284018 gdlmx   14u     unix 0xffff9cd6c5bf4800      0t0 49443204 filename
--- To close ---
6 :
ZMQbg/1 284018 gdlmx   14u     unix 0xffff9cd6c5bf4800      0t0 49443204 filename
7 :

Я использовал python и ipython для запуска скрипта и получил тот же результат.

В заключение, файл сокета или сетевой порт открыт только при socket.bind. Во время моих тестов никакие другие сокеты не открывались процессами python/ipython.

Обновить

В ответ на обновление ПО:

Ненормальное (неожиданное) поведение, вероятно, вызвано предварительно собранными двоичными файлами, включенными в пакет pyzmq на PyPI. pip install pyzmq загрузит этот дистрибутив tar-шара из PyPI, который содержит следующие предварительно скомпилированные двоичные файлы:

zmq/backend/cython:
    _device.so  _proxy_steerable.so  constants.so  error.so    socket.so
    _poll.so    _version.so          context.so    message.so  utils.so

zmq/.libs:
    libzmq-39117701.so.5.2.1         libsodium-72341b7d.so.23.2.0

Чтобы быть совместимыми с максимально возможным количеством ОС Linux, эти двоичные файлы создаются в очень старой ОС (CentOS 5) в среде докеров, называемой manylinux.

Anaconda использует другой подход для предварительной сборки двоичных файлов и содержит все зависимости в папке conda/envs. Таким образом, их двоичные файлы построены в относительно современной среде.

Я протестировал двоичные файлы PyPI на своем компьютере CentOS 7 с помощью приведенного выше сценария. Я могу подтвердить, что ZeroMQ открывает некоторые "фоновые" сокеты (2 сокета после создания контекста и 8 после первого создания сокета). Хотя мои тесты ниже показывают, что они используются для связи между потоками для внутренних механизмов ZeroMQ, лучше обратиться напрямую к сопровождающим пакета PyPI.

Вы также можете попытаться заставить pip/setuptools собрать ZeroMQ для вашей ОС:

sudo yum install libzmq3-devel #  RHEL-based
pip install --no-use-wheel pyzmq 
# Use '--no-binary :all:' instead of '--no-use-wheel' in pip >= 10.0.0

Это может избавить от фоновых сокетов, если это то, что вы хотите.

Какое назначение фоновых розеток?

ZeroMQ внутренне использует несколько потоков для операции ввода-вывода. Количество потоков можно настроить с помощью IO_THREADS. Я считаю, что это число влияет на количество используемых сокетов. Проверьте это с

num_io_threads = int(sys.argv[1])
c = zmq.Context()
c.set(zmq.IO_THREADS,num_io_threads)
s = c.socket(zmq.PUSH)
lsof()

Вы найдете, что number_of_sockets= 6 + 2 * num_io_threads. Таким образом, я постулирую, что двоичные файлы ZeroMQ из PyPI внутренне используют сокеты для связи между потоками между основным потоком и рабочим /IO-потоками.

Ответ 2

iPython/Jupyternotebook использует Zero MQ (https://jupyter-client.readthedocs.io/en/stable/messaging.html) для обмена сообщениями. Я предлагаю запустить тест непосредственно в Python, чтобы получить точное количество открытых сокетов.