У меня есть функция sender(), которая отправляет UDP-пакет. В конце sender() он пробуждает поток-получатель для ожидания ответа UDP с таймаутом.
Здесь sender() может быть вызван основным потоком или потоком-получателем. Поток-получатель после получения ответного сообщения или таймаута может принять решение об отправке нового пакета. Вот почему sender() также должна иметь возможность вызывать из контекста получателя. Код песудо выглядит следующим образом:
std::mutex m;
std::conditional_variable reciever_cv;
void sender()
{
request = create_new_packet();
socket.sendBytes( request );
receiver_cv.notify_one();
}
// receiver() gets started as a thread when system is up
void receiver()
{
while(true)
{
std::uniq_lock lock(m);
receiver_cv.wait( lock, [](){ return predicate; }); // predicate could be anything
socket.receiveBytes();
// ... some processing
if ( new packet needs to be sent )
{
sender();
}
}
}
Мой вопрос заключается в том, может ли поток уведомить себя о пробуждении в следующем цикле?
Моя задача проста. Единственная сложность заключается в том, что существует функция, совместно используемая разными потоками. Я надеюсь, что номер пробуждения каким-то образом запоминается, и получатель просто просыпается, чтобы соответствовать этому номеру.
Пока что все онлайн-материалы, которые я просматривал, содержат предложение о выдаче уведомления в отдельной теме.
Будет ли считать семафор лучшим подходом в моем случае?
notify уведомляет waitо тредах. Вы не можете быть одновременно уведомителем и ожидающим.
Даже если ваш получатель что-то отправляет, разве это не должно быть отправлено какому-то другому объекту? Какой смысл получателю отправлять данные самому себе? (и не вызовет ли это бесконечный цикл приема и отправки?)
Каждый поток в каждой точке имеет стек вызовов. Не могли бы вы уточнить, как именно этот стек вызовов должен выглядеть в вашем случае: вы ожидаете, что это будет receiver()->sender()->notify_one()->...->receiver() или что-то еще?
@wohlstad sender() использует сокет для отправки UDP-пакета на другой хост в Интернете. Таким образом, данные отправляются не в поток-получатель, а на другой конец сокета.
@Abstraction Я перекодировал код песудо. sender() следует вызывать внутри потока-получателя только при определенных условиях. Если условие не выполнено, то звонка sender() больше не будет. Например, сделайте повторную передачу несколько раз.
@neuronmac, если получатель отправляет сообщение на другой хост в Интернете, получатель на другой стороне будет другим процессом и другим потоком, не так ли? Боюсь, я не понимаю вашей установки. Возможно, вы могли бы уточнить в вопросе.
Поток, застрявший в ожидании, очевидно, не может уведомить себя. Вот почему циклы событий, которые вы найдете в таких библиотеках, как boost::asio, имеют тенденцию быть немного более сложными; они не просто ждут условной переменной.
static int Counter; ?? блокировка мьютекса перед изменением счетчика.
@wohlstad Допустим, у нас есть два хоста A и B с разными IP-адресами. Моя программа работает на хосте A. Моя программа отправляет данные на B, а затем получает ответ от B. Моя программа имеет два потока «основной» и «получатель», последний ожидает ответа от B на A. Оба они вызывают sender() для отправки пакета. к Б.
Эээ, нет, потому что, если бы он ждал, он не мог бы работать, а если бы он работал, он не мог бы ждать.





Мой вопрос заключается в том, может ли поток уведомить себя о пробуждении в следующем цикле?
Нет, но вы можете избежать ненужного ожидания.
В вашем примере существенно отсутствует одна вещь — типичный (атомарный) bool, который сигнализирует, стоит ли вам ждать:
std::mutex m;
std::conditional_variable reciever_cv;
// with a std::atomic<bool>, you could also avoid locking the mutex within sender()
bool waiting_for_response = false;
void sender()
{
request = create_new_packet();
socket.sendBytes( request );
{
std::scoped_lock l( m );
waiting_for_response = true;
}
receiver_cv.notify_one();
}
void receiver()
{
while(true)
{
{
std::unique_lock lock(m);
receiver_cv.wait( lock, [](){ return waiting_for_response; });
socket.receiveBytes();
waiting_for_response = false;
}
// ...
sender(); // safe
Вызов sender() теперь будет безопасным и вообще не будет ждать блокировки, поскольку предикат проверяется перед вызовом wait() (без предиката).
Обычно вам нужен такой bool, чтобы справиться с ложным пробуждением,
и это, вероятно, все, что вам нужно для вашего варианта использования.
Однако на самом деле это не поток, уведомляющий сам себя.
Однако эта конструкция все еще весьма несовершенна. Самая большая проблема заключается в том, что он неправильно обрабатывает вызов sender(), когда получатель уже ожидает, поэтому одновременно можно ожидать только один пакет.
На самом деле вам нужно что-то вроде std::counting_semaphore для правильного дизайна для одного производителя и одного потребителя с более чем одним элементом.
См. также Вариант использования счетного семафора
Вы также можете избавиться как от условной переменной, так и от мьютекса, если просто используете std::atomic<bool>::wait/std:atomic<bool>::notify, добавленный в C++20.
См. также std::atomic<bool>::wait и std::condition_variable::wait.
Используя std::atomic<int>, вы даже можете расширить этот подход для одновременной обработки нескольких пакетов.
Спасибо за пример кода. Есть вопрос... получатель сначала блокирует мьютекс, используя unique_lock. Не снимая блокировки, sender() пытается снова заблокировать тот же мьютекс, используя scoped_lock в вашем коде. Будет ли это проблемой?
@neuronmac да, это будет UB (я думаю? может быть, взаимоблокировка? но взаимоблокировки - это UB ...) без использования рекурсивного мьютекса. Я исправил ответ, чтобы он сейчас снял блокировку.
Ваша тема не
waitпри вызовеnotify_one. Следовательно, его вообще нельзя уведомить. Будет кто-то другой. Если вы хотите, чтобыwait()разбудилsender(), вам действительно следует каким-то образом включить это вpredicate, чтобы иметь точную семантику. В противном случае все это кажется мне очень ТОКТУ-склонным.