2

Is there a way to start two (or more) C++11 threads and join() the first one that is finished?

An example scenario:

#include <iostream>
#include <thread>

using namespace std;    
void prepare_item1() {std::cout << "Preparing 1" << std::endl;}    
void consume_item1() {std::cout << "Consuming 1" << std::endl;}    
void prepare_item2() {std::cout << "Preparing 2" << std::endl;}    
void consume_item2() {std::cout << "Consuming 2" << std::endl;}

int main()
{
    std::thread t1(prepare_item1);
    std::thread t2(prepare_item2);

    t1.join();
    consume_item1();

    t2.join();
    consume_item2();

    return 0;
}

I would have liked to do something like that instead:

int main()
{
    std::thread t1(prepare_item1);
    std::thread t2(prepare_item2);    

    finished_id=join_any(t1,t2)
    if (finished_id==1)
    {
        consume_item1();
        ...
    }
    else if (finished_id==2)
    {
        consume_item2();
        ...
    }

    return 0;
}

Also, I would like the solution to be blocking, similar to the t.join() function.

Note: The real reason I need this is that I have two different blocking functions from which I receive commands, and whenever any of them is ready I would like to process the first command that arrives and continue to the next one when it is done. (sequential processing of commands from two parallel sources)

Thanks!

MZHm
  • 2,388
  • 1
  • 18
  • 24
  • I would initially say no, but I'm sure if you register interrupts you might be able to do it in that specific manner. – SGM1 Jan 21 '16 at 21:35
  • 1
    What you need is `std::condition_variable` http://en.cppreference.com/w/cpp/thread/condition_variable – arainone Jan 21 '16 at 21:36
  • 1
    To explain further, the `std::condition_variable` would be shared across all threads. The main thread, the one in which you block, will be signaled by whichever thread (t1 or t2) that calls `notify_one` first. – arainone Jan 21 '16 at 21:41
  • 1
    This is a single most ugly thing in Posix thread model, which C++ threading model is based on - inability to join more than one thread in a single call. Hate Posix for that. – SergeyA Jan 21 '16 at 23:00
  • @SergeyA is there an an alternative model you could suggest that supports joining more than one thread? (I am oped to use well-supported/popular libraries if they provide a solution) – MZHm Jan 22 '16 at 14:30
  • 1
    @MZHm, what's your OS? If you are on Windows, you can simply use `WaitForMultipleObjects`. If you are on Posix-compliant system, you will have to sweat and develop your own mechanism for signalling thread completion - for example, by using an array of thread ids and conditional variable to wait on. Every thread than will update the vector with it's id and signal the variable before exiting. This is cumbersome, and if thread crashes, will not be registered. But there is nothing else. – SergeyA Jan 22 '16 at 14:34
  • The OS is ubuntu. But I want to keep the code as OS-independent as possible. – MZHm Jan 22 '16 at 18:17

1 Answers1

2

Here is a thread-safe multi-producer multi-consumer queue:

template<class T>
struct safe_queue {
  std::deque<T> data;
  std::atomic<bool> abort_flag = false;
  std::mutex guard;
  std::condition_variable signal;

  template<class...Args>
  void send( Args&&...args ) {
    {
      std::unique_lock<std::mutex> l(guard);
      data.emplace_back(std::forward<Args>(args)...);
    }
    signal.notify_one();
  }
  void abort() {
    abort_flag = true;   // 1a
    { std::unique_lock<std::mutex>{guard}; }
    signal.notify_all(); // 1b
  }        
  std::experimental::optional<T> get() {
    std::unique_lock<std::mutex> l(guard);
    signal.wait( l, [this]()->bool{ // 2b
      return !data.empty() || abort_flag.load(); // 2c
    });
    if (abort_flag.load()) return {};
    T retval = std::move(data.front());
    data.pop_front();
    return retval;
  }
};

have the threads shove data into the queue, and the main thread do a .get() on it.

If abort() is called, all waiting threads are woken up with an "empty" value from .get().

It uses std::experimental::optional, but you can replace that with something else (throw on abort? Whatever).

Code modified slightly from this other answer. Note that I think the other answer has some errors in it, which I corrected above, and attempts to solve a different problem.

The message you send could be the id of the thread that is ready to be waited upon, for example, or the work it has completed, or whatever.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524