Python Socket получает / отправляет многопоточность - PullRequest
0 голосов
/ 29 июня 2018

Я пишу программу на Python, в которой в основном потоке я непрерывно (в цикле) получаю данные через TCP-сокет, используя функцию recv. В функции обратного вызова я отправляю данные через тот же сокет, используя функцию sendall. То, что вызывает обратный вызов, не имеет значения. Я установил в своем сокете блокировку.

Мой вопрос: это безопасно делать? Насколько я понимаю, функция обратного вызова вызывается в отдельном потоке (не в основном потоке). Является ли объект сокета Python потокобезопасным? Из моего исследования я получил противоречивые ответы.

1 Ответ

0 голосов
/ 29 июня 2018

Сокеты в Python не являются потокобезопасными.

Вы пытаетесь решить сразу несколько проблем:

  1. Розетки не являются поточно-ориентированными.
  2. recv блокирует и блокирует основной поток.
  3. sendall используется из другого потока.

Вы можете решить их, используя asyncio или решив способ, которым asyncio решает его внутренне: используя select.select вместе с socketpair и используя очередь для входящих данных.

import select
import socket
import queue

# Any data received by this queue will be sent
send_queue = queue.Queue()

# Any data sent to ssock shows up on rsock
rsock, ssock = socket.socketpair()

main_socket = socket.socket()

# Create the connection with main_socket, fill this up with your code

# Your callback thread
def different_thread():
    # Put the data to send inside the queue
    send_queue.put(data)

    # Trigger the main thread by sending data to ssock which goes to rsock
    ssock.send(b"\x00")

# Run the callback thread

while True:
    # When either main_socket has data or rsock has data, select.select will return
    rlist, _, _ = select.select([main_socket, rsock], [], [])
    for ready_socket in rlist:
        if ready_socket is main_socket:
            data = main_socket.recv(1024)
            # Do stuff with data, fill this up with your code
        else:
            # Ready_socket is rsock
            rsock.recv(1)  # Dump the ready mark
            # Send the data.
            main_socket.sendall(send_queue.get())

Мы используем несколько конструкций здесь. Вам нужно будет заполнить пустые места с вашим кодом выбора. Что касается объяснения:

Сначала мы создаем send_queue, который представляет собой очередь данных для отправки. Затем мы создаем пару соединенных сокетов (socketpair()). Это понадобится нам позже, чтобы разбудить основной поток, так как мы не хотим, чтобы recv() блокировал и предотвращал запись в сокет.

Затем мы подключаем main_socket и запускаем поток обратного вызова. Теперь вот магия:

В основном потоке мы используем select.select, чтобы узнать, есть ли у rsock или main_socket какие-либо данные. Если у одного из них есть данные, основной поток просыпается.

После добавления данных в очередь мы активируем основной поток, сигнализируя ssock, который просыпается rsock и, таким образом, возвращается из select.select.

Чтобы полностью понять это, вам нужно прочитать select.select(), socketpair() и queue.Queue().

...