3

I would like to implement branch and bound search in a multithreaded manner. In particular, I want to use async to wrap the search calls at each branch, then just wait until some thread comes up with the answer, and just exit. (Ideally, I would want to cancel the other threads, but thread cancelling is not in the standard). Here is some simplified code :

#include <iostream>
#include <random>
#include <future>
#include <thread>

using namespace std;

mt19937 rng;
uniform_int_distribution<unsigned> random_binary(0, 1);

bool search() {
  return static_cast<bool>(random_binary(rng));
}

#define N 10000

int main()
{
  rng.seed(42);

  std::vector<future<bool>> tasks;

  for (unsigned i=0; i<N; ++i)
    tasks.push_back(async(launch::async, search));

  // Don't want to wait sequentially here.
  for (unsigned i=0; i<N; ++i) {
    tasks[i].wait();
    if (tasks[i].get()) {
      cout << "i = " << i << "\n";
      break;
    }
  }
  return 0;
}

search() is the search function. It returns true/false based on whether it found the answer or not. I return a random answer for illustration. But the crux of the problem is in the for loop that calls tasks[i].wait(). Right now, I am waiting sequentially for tasks to finish. Instead I want to do something like this :

auto x = wait_for_any(tasks.begin(), tasks.end());
x.get();
// cancel other threads.
// Profit?

What is a good way to achieve this?

keveman
  • 8,427
  • 1
  • 38
  • 46
  • BTW you're already in UB-land here by having multiple threads call `random_binary(rng)`, which is _not_ thread-safe. – ildjarn Aug 11 '12 at 02:55

3 Answers3

8

std::future provides a valid() function that lets you check if the result is available without blocking, so you can just use that, e.g. in a busy-wait loop:

std::future<bool>* res_future = 0;
for(size_t i = 0; ; i==tasks.size()?i=0:++i){
  // could add a timeout period to not completely busy-wait the CPU
  if(tasks[i].wait_for(std::chrono::seconds(0)) == std::future_status::ready){
    res = &tasks[i];
    break;
  }
}

bool res = res_future->get();

A proposed addition to std::future, to make tasks like this easier, is a .then(func_obj) method that asynchronously calls the func_obj when the result is available, where you could set a flag or something.

I sadly don't know of a way to possibly implement wait_for_any in any other way than above. :/

template<class FwdIt>
std::future<bool> wait_for_any(FwdIt first, FwdIt last)
{
  return std::async([=]{
    for(FwdIt cur(first); ; cur==last?cur=first:++cur){
    // could add a timeout period to not completely busy-wait the CPU
    if(cur->wait_for(std::chrono::seconds(0)) == std::future_status::ready)
      return cur->get();
  });
}

Thread destruction is usually done with cooperative cancellation.

P. S.: std::future<T>::get() will automatically wait() if the result is not available.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • your code is much better than what I have, but it's still sequentially and synchronously polling each thread, though. I wonder if there is a more asynchronous solution. – keveman Aug 10 '12 at 22:55
  • 2
    @keveman: I think you might need to abandon `std::future`/`std::async` and put something together yourself with `std::condition_variable`. – Xeo Aug 10 '12 at 23:01
  • @keveman: Take a look at the edit, it's now "more asynchronous" in that the main thread isn't blocked until requested. ;) – Xeo Aug 10 '12 at 23:20
6

Arrange for all tasks to have access to the same condition_variable, mutex, and bool. This could be done by making these globals, or member data where each task is running a member function, or you could pass them via std::ref as arguments in the task creation function.

Initialize the bool to not_found prior to starting any tasks. The main thread then launches the tasks and waits on the condition_variable. The searcher tasks then search. As they search, they occasionally inspect the bool (perhaps with an atomic load) to see if it has been set to found. If it has, the searcher thread returns immediately.

When one thread finds the results, it sets the bool to found and signals the condition_variable. This will wake the main thread and effectively cancel the rest of the searcher tasks. The main thread can then either join, detach, abandon, whatever, with all of the searcher tasks. It would be best if you could arrange for all of the searcher tasks to end prior to main exiting if you don't have main explicitly join the searcher tasks.

No polling. No waiting for dead-end searches. The only ad-hoc part is figuring out how and how often for the searcher tasks to inspect the bool. I recommend performance testing this part.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Inspecting the `bool` part is kind of intrusive for my use case. I started off with single threaded code, but wanted to run the searches on a branch in parallel. I am guessing this is not an uncommon scenario when trying to parallelize existing code base. Of course, I will bite the bullet and do the intrusive modifications, but I wish I had thread::cancel. – keveman Aug 10 '12 at 23:25
  • @keveman: No, you don't. It makes no sense to cancel a thread without the threads cooperation. What if it locked a mutex and can't unlock it because you killed it? That's what cooperative cancellation is for. – Xeo Aug 10 '12 at 23:29
  • @Xeo Look at my use case again, I am starting from sequential code, with no mutex or any such stuff. I agree, thread::cancel could potentially cause deadlocks and such because the thread being cancelled could be in any state, and should be carefully used. However, I just opining that thread::cancel would be useful for porting over certain classes of sequential programs. – keveman Aug 10 '12 at 23:43
  • @keveman: I wish you had thread::cancel too. I tried my best to get it standardized and failed. It would've been a cooperative cancellation. I.e. your thread would have had to execute an "cancellation point" to even notice the cancellation. Otherwise the the scenario Xeo points out would be all too common. But a standardized facility for this would've been much better than we have today. As it is, you have to fake a "cancellation point" manually with a bool and explicit checking. But that is all the automated system could have done. There's no magic behind the curtain. – Howard Hinnant Aug 11 '12 at 02:46
  • How would that work if the called code has no access to the thread? Or would the thread pass `this` as an argument to the call? Or would it be some magic with `std::this_thread`? – Xeo Aug 11 '12 at 10:14
  • There would have to be a bool stored in thread local storage for each std::thread. boost::thread actually implements this. The boost implementation was done to demonstrate that it is possible to implement. Nevertheless there were strongly held opinions on the committee that it was not possible to implement. On boost it is called `interrupt` for historical reasons. `cancel` became such a dirty word that you could start an hour long argument by simply walking into a room crowded with C++ experts and saying "cancel" in a loud voice. :-) – Howard Hinnant Aug 11 '12 at 15:46
0

The approach that I would take is to have the main function create a std::promise which is then passed as a reference to the various threads that are going to do the work. Each of the working threads would then use the std::promise they all share to use set_value() once they have the result.

Which ever working thread gets there first will be able to send a result and the others will get an exception thrown when they try to use set_value() and it has already been set.

In the source code skeleton that follows I am using std::async but it would be nice if we could just spin off autonomous threads without creating the additional std::promise which is not used for anything.

So the code skeleton would look something like:

#include <iostream>
#include <thread>  // std::thread is defined here
#include <future>  // std::future and std::promise defined here

bool search(int args, std::promise<int> &p) {

    // do whatever needs to be done

    try {
        p.set_value(args);
    }
    catch (std::future_error & e) {
        // std::future_errc::promise_already_satisfied
    }
    return true;
}
int main(int argc, char * argv[])
{
    std::promise<int> myProm;    
    auto fut = myProm.get_future();

    for (int i = 1; i < 4; i++) {
        std::async(std::launch::async, search, i, std::ref(myProm));
    }

    auto retVal = fut.get();
    return 0;
}
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106