1

I have a sender() function, which sends out UDP packet. At the end of the sender() it wakes up a receiver thread to wait for the UDP response with a timeout.

Here the sender() could be called by main thread or by the receiver thread. Receiver thread after receiving a response message or timeout, it may decide to send a new packet. That's why, the sender() should be able to called from receiver's context too. The pesudo code is as follows:

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();
  }
 }
}

My question here is that can a thread notify itself to wakeup in the next loop?
My task is simple. The only complication is there is a function shared by different threads. I hope the number of wakeup is memorized somehow, and the receiver just wakes up to match that number.

So far, all online materials I have browsed give suggestion on issuing notification from a separate thread.

Would counting semaphore a better approach in my case?

11
  • 2
    Your thread does not wait when it calls notify_one. Hence, it cannot be notified at all. Someone else will be. If you want wait() to be woken up by sender(), you should really include that into predicate somehow to have a precise semantics. Otherwise, it all seems very TOCTOU-prone tome.
    – yeputons
    Commented May 16 at 8:06
  • notify notifies waiting threads. You can't be both a notifier and waiting at the same time. Commented May 16 at 8:07
  • Even if your receiver is sending something, shouldn't it be sent to some other entity ? What is the point of the receiver sending some data to itself ? (and will it not trigger an infinite loop of receiving and sending ?)
    – wohlstad
    Commented May 16 at 8:07
  • Every thread at every point has a callstack. Could you clarify how exactly that callstack should look in your case: do you expect it to be receiver()->sender()->notify_one()->...->receiver() or something else? Commented May 16 at 8:07
  • 1
    A thread that's stuck in a wait cannot, obviously, notify itself. That's why event loops that you find in library like boost::asio tend to be slightly more involved; they aren't just waiting for a condition variable. Commented May 16 at 8:25

1 Answer 1

0

My question here is that can a thread notify itself to wakeup in the next loop?

No, but you can prevent unnecessarily waiting. One thing crucially missing from your example is the typical (atomic) bool that signals whether you should wait:

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

Calling sender() would now be safe and not lock wait at all because the predicate is checked before calling wait() (without a predicate). You typically need such a bool anyway to deal with spurious wakeup, and that's probably all you need to cover your use case. This is not really a thread notifying itself though.

However, this design is still quite flawed. The biggest issue is that it doesn't properly handle when sender() is called while the receiver is already waiting, so you can only be waiting for one packet at a time. You actually need something like a std::counting_semaphore for a proper single-producer, single-consumer design with more than one element. See also The usage case of counting semaphore

You can also get rid of both the condition variable and the mutex if you simply use std::atomic<bool>::wait/std:atomic<bool>::notify, added in C++20. See also std::atomic<bool>::wait vs. std::condition_variable::wait. With the use of std::atomic<int>, you could even extend that approach to handle multiple packets simultaneously.

2
  • 1
    Thank you for sample code. Got a question... the receiver lock the mutex using unique_lock first. Without releasing the lock, sender() tries to lock the same mutex again using scoped_lock in your code. Would this be a problem?
    – neuron mac
    Commented May 16 at 9:05
  • @neuronmac yes, this would be UB (I think? maybe deadlock? but deadlocks are UB ...) without using a recursive mutex. I've corrected the answer so that it releases the lock now. Commented May 16 at 9:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.