В C++ может ли поток, ожидающий условной переменной, уведомить себя?

У меня есть функция 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();
  }
 }
}

Мой вопрос заключается в том, может ли поток уведомить себя о пробуждении в следующем цикле?
Моя задача проста. Единственная сложность заключается в том, что существует функция, совместно используемая разными потоками. Я надеюсь, что номер пробуждения каким-то образом запоминается, и получатель просто просыпается, чтобы соответствовать этому номеру.

Пока что все онлайн-материалы, которые я просматривал, содержат предложение о выдаче уведомления в отдельной теме.

Будет ли считать семафор лучшим подходом в моем случае?

Ваша тема не wait при вызове notify_one. Следовательно, его вообще нельзя уведомить. Будет кто-то другой. Если вы хотите, чтобы wait() разбудил sender(), вам действительно следует каким-то образом включить это в predicate, чтобы иметь точную семантику. В противном случае все это кажется мне очень ТОКТУ-склонным.

yeputons 16.05.2024 10:06
notify уведомляет waitо тредах. Вы не можете быть одновременно уведомителем и ожидающим.
Jean-Baptiste Yunès 16.05.2024 10:07

Даже если ваш получатель что-то отправляет, разве это не должно быть отправлено какому-то другому объекту? Какой смысл получателю отправлять данные самому себе? (и не вызовет ли это бесконечный цикл приема и отправки?)

wohlstad 16.05.2024 10:07

Каждый поток в каждой точке имеет стек вызовов. Не могли бы вы уточнить, как именно этот стек вызовов должен выглядеть в вашем случае: вы ожидаете, что это будет receiver()->sender()->notify_one()->...->receiver() или что-то еще?

Abstraction 16.05.2024 10:07

@wohlstad sender() использует сокет для отправки UDP-пакета на другой хост в Интернете. Таким образом, данные отправляются не в поток-получатель, а на другой конец сокета.

neuron mac 16.05.2024 10:15

@Abstraction Я перекодировал код песудо. sender() следует вызывать внутри потока-получателя только при определенных условиях. Если условие не выполнено, то звонка sender() больше не будет. Например, сделайте повторную передачу несколько раз.

neuron mac 16.05.2024 10:17

@neuronmac, если получатель отправляет сообщение на другой хост в Интернете, получатель на другой стороне будет другим процессом и другим потоком, не так ли? Боюсь, я не понимаю вашей установки. Возможно, вы могли бы уточнить в вопросе.

wohlstad 16.05.2024 10:20

Поток, застрявший в ожидании, очевидно, не может уведомить себя. Вот почему циклы событий, которые вы найдете в таких библиотеках, как boost::asio, имеют тенденцию быть немного более сложными; они не просто ждут условной переменной.

Christian Stieber 16.05.2024 10:25
static int Counter; ?? блокировка мьютекса перед изменением счетчика.
James 16.05.2024 10:25

@wohlstad Допустим, у нас есть два хоста A и B с разными IP-адресами. Моя программа работает на хосте A. Моя программа отправляет данные на B, а затем получает ответ от B. Моя программа имеет два потока «основной» и «получатель», последний ожидает ответа от B на A. Оба они вызывают sender() для отправки пакета. к Б.

neuron mac 16.05.2024 10:46

Эээ, нет, потому что, если бы он ждал, он не мог бы работать, а если бы он работал, он не мог бы ждать.

user207421 16.05.2024 11:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мой вопрос заключается в том, может ли поток уведомить себя о пробуждении в следующем цикле?

Нет, но вы можете избежать ненужного ожидания. В вашем примере существенно отсутствует одна вещь — типичный (атомарный) 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 в вашем коде. Будет ли это проблемой?

neuron mac 16.05.2024 11:05

@neuronmac да, это будет UB (я думаю? может быть, взаимоблокировка? но взаимоблокировки - это UB ...) без использования рекурсивного мьютекса. Я исправил ответ, чтобы он сейчас снял блокировку.

Jan Schultke 16.05.2024 11:45

Другие вопросы по теме

Похожие вопросы

Обработка деления числа double на большую степень 2 в C++
Многопоточный push_back в std::vector: мьютекс, увеличить и отредактировать на месте или создать вектор для результатов и отправить его обратно?
Специализация класса C++ Enable_if не вызывается
Почему od и мой код на C++ читаются с другим порядком байтов, чем тот, который отображается шестнадцатеричными редакторами?
Существуют ли в C++ «функции массового перемещения», которые избегают множественных назначений перемещения, точно так же, как std::memmove избегает множественных назначений копирования?
Влияйте на выравнивание классов, чтобы плотно упаковывать переменные
Ошибка C3867: нестандартный синтаксис; используйте '&', чтобы создать указатель на член
Почему изменяемая лямбда преобразуется в указатель на функцию вместо вызова оператора()?
Генерация уникальных ключей, которые будут использоваться, например. unordered_map для функций-членов
Алгоритм сортировки, который сортирует каждые N элементов массива