3

I have three threads in my application, the first thread needs to wait for a data to be ready from the two other threads. The two threads are preparing the data concurrently. In order to do that I am using condition variable in C++ as following:

boost::mutex mut;       
boost::condition_variable cond;     

Thread1:

    bool check_data_received()
    {
         return (data1_received && data2_received);
    }

    // Wait until socket data has arrived
    boost::unique_lock<boost::mutex> lock(mut);
    if (!cond.timed_wait(lock, boost::posix_time::milliseconds(200),
        boost::bind(&check_data_received)))
    {

    }

Thread2:

    {
        boost::lock_guard<boost::mutex> lock(mut);
        data1_received = true;
    }
    cond.notify_one();

Thread3:

    {
        boost::lock_guard<boost::mutex> lock(mut);
        data2_received = true;
    }
    cond.notify_one();

So my question is it correct to do that, or is there any more efficient way? I am looking for the most optimized way to do the waiting.

IoT
  • 607
  • 1
  • 11
  • 23
  • 1
    "most optimized way" to "do the waiting" is a surprisingly powerful contradiction :) – sehe Apr 02 '14 at 12:45
  • @sehe I know doing waiting is not good, and will effect the parallelism of the work. That's why I want to know,if there's another way to achieve what I want :) – IoT Apr 02 '14 at 12:51

1 Answers1

2

It looks like you want a semaphore here, so you can wait for two "resources" to be "taken".

For now, just replace the mutual exclusion with an atomic. you can still use a cv to signal the waiter:

#include <boost/thread.hpp>

boost::mutex mut;       
boost::condition_variable cond;     

boost::atomic_bool data1_received(false);
boost::atomic_bool data2_received(false);

bool check_data_received()
{
    return (data1_received && data2_received);
}

void thread1()
{
    // Wait until socket data has arrived
    boost::unique_lock<boost::mutex> lock(mut);
    while (!cond.timed_wait(lock, boost::posix_time::milliseconds(200),
        boost::bind(&check_data_received)))
    {
        std::cout << "." << std::flush;
    }
}

void thread2()
{
    boost::this_thread::sleep_for(boost::chrono::milliseconds(rand() % 4000));
    data1_received = true;
    cond.notify_one();
}

void thread3()
{
    boost::this_thread::sleep_for(boost::chrono::milliseconds(rand() % 4000));
    data2_received = true;
    cond.notify_one();
}

int main()
{
    boost::thread_group g;
    g.create_thread(thread1);
    g.create_thread(thread2);
    g.create_thread(thread3);

    g.join_all();
}

Note:

  • warning - it's essential that you know only the waiter is waiting on the cv, otherwise you need notify_all() instead of notify_one().
  • It is not important that the waiter is already waiting before the workers signal their completion, because the predicated timed_wait checks the predicate before blocking.
  • Because this sample uses atomics and predicated wait, it's not actually critical to signal the cv under the mutex. However, thread checkers will (rightly) complain about this (I think) because it's impossible for them to check proper synchronization unless you add the locking.
sehe
  • 374,641
  • 47
  • 450
  • 633
  • tahnks for your answer but what's the difference with respect to my original code. You're still using the predicated wait, but for workers thread you replaced the lock_guard with atomic_bool. What performance improvement will this do? – IoT Apr 02 '14 at 13:13
  • no locking to read/write the shared state (bools). If you expect continuous loads, it could be beneficial to "spin" on the condition without a mutex here. Then you've arrived at lockfree synchronization. However, there's no blocking wait in lock-free programming, so if there's no continuous work, this will end up just burning CPU while spinning. See e.g. http://stackoverflow.com/questions/22486552/boostlockfreespsc-queue-busy-wait-strategy-is-there-a-blocking-pop/22487474#22487474 – sehe Apr 02 '14 at 13:28
  • If you want I can show you a solution based on thead_barrier later. Have to go now – sehe Apr 02 '14 at 13:29
  • ok whenever you have time, I will be happy to see the thread_barrier – IoT Apr 02 '14 at 13:39
  • Ok. Here's the spinning version (actually, it would mostly be useful without the sleep in `thread1` since, if you need it locking would not be a problem (see my answer solution) and otherwise it would pin the CPU 100%) http://coliru.stacked-crooked.com/a/119b35fea470acfe – sehe Apr 02 '14 at 15:14
  • And here's the version with the thread-barrier: http://coliru.stacked-crooked.com/a/f0cee581a228fa4f. Note, I decided to make the number of workers variable, and show how the barrier can be reused. In fact, you can vary the semantics by constructing the barrier with a custom `completion function`. This resembles an inverse semaphore, if you will. Also, I'm not sure what the performance characteristics here would be. I guess it comes down to profiling, profiling, profiling (as always) – sehe Apr 02 '14 at 15:28