У меня есть рабочий поток, который прослушивает сокет TCP для входящего трафика и буферизует полученные данные для доступа к основному потоку (давайте назовем этот сокет A). Однако рабочий поток также должен выполнять некоторые регулярные операции (скажем, один раз в секунду), даже если данные не поступают. Поэтому я использую select()
с тайм-аутом, чтобы мне не нужно было продолжать опрос. (Обратите внимание, что вызов receive()
в неблокирующем сокете, а затем засыпание на секунду нехорошо: входящие данные должны быть немедленно доступны для основного потока, даже если основной поток не всегда может сразу их обработать, поэтому необходимость буферизации.)
Теперь мне также нужно иметь возможность сигнализировать рабочему потоку, чтобы он немедленно сделал что-то другое; из основного потока, мне нужно, чтобы рабочий поток select()
сразу же вернулся. На данный момент я решил это следующим образом (подход в основном взят из здесь и здесь):
При запуске программы рабочий поток создает для этого дополнительный сокет типа дейтаграмма (UDP) и привязывает его к какому-то случайному порту (назовем этот сокет B). Аналогичным образом основной поток создает сокет для отправки дейтаграммы. При вызове select()
рабочий поток теперь перечисляет как A, так и B в fd_set
. Когда основной поток должен подать сигнал, он sendto()
отправляет пару байтов на соответствующий порт на localhost
. В рабочем потоке, если B остается в fd_set
после возврата select()
, вызывается recvfrom()
, а полученные байты просто игнорируются.
Кажется, это работает очень хорошо, но я не могу сказать, что мне нравится это решение, главным образом потому, что оно требует привязки дополнительного порта для B, а также потому, что оно добавляет несколько дополнительных вызовов API сокетов, которые могут дать сбой. догадываюсь – и мне не очень хочется выяснять подходящее действие для каждого из случаев.
Я думаю, что в идеале я хотел бы вызвать некоторую функцию, которая принимает A в качестве входных данных и ничего не делает, кроме немедленного возврата select()
. Однако я не знаю такой функции. (Думаю, я мог бы, например, shutdown()
сокет, но побочные эффекты не очень приемлемы :)
Если это невозможно, вторым лучшим вариантом будет создание B, который намного проще, чем настоящий сокет UDP, и на самом деле не требует выделения каких-либо ограниченных ресурсов (кроме разумного объема памяти). . Я предполагаю, что сокеты домена Unix сделают именно это, но: платформу, чем та, что у меня есть в настоящее время, хотя некоторое умеренное количество #ifdef
материала в порядке. (Я ориентируюсь в основном на Windows и Linux — и, кстати, пишу на C++.)
Пожалуйста, не предлагайте рефакторинг, чтобы избавиться от двух отдельных потоков. Такой дизайн необходим, потому что основной поток может быть заблокирован на длительное время (например, при выполнении каких-то интенсивных вычислений — и я не могу начать периодически вызывать receive()
из самого внутреннего цикла вычислений), а тем временем кому-то нужно буферизовать входящие данные (и по причинам, которые я не могу контролировать, это не может быть отправителем).
Теперь, когда я писал это, я понял, что кто-то обязательно ответит просто "Boost.Asio", так что я только что впервые посмотрел на это... Однако не смог найти очевидного решения. Обратите внимание, что я также не могу (легко) повлиять на то, как создается сокет A, но при необходимости я должен позволить другим объектам обернуть его.