4

I have been struggling for days to come up with a mechanism for launching a few timers and not having it clock the main program execution. Combinations of .join() and .detach(), wait_until(), etc

What I have is a vector of std::thread and I want to:

  • execute the first position
  • wait for it to finish
  • execute the next position
  • wait for it to finish

meanwhile the rest of my app is running along, users clicking things, etc. Everything I come up with seems to either:

  • block the main program from running while the timers are going

or

  • detach from the main thread but then the timers run concurrently, how I want one after the previous one has finished.

I even posted: C++11 std::threads and waiting for threads to finish but no resolution that I can seem to make sense of either.

should I be using std::launch::async maybe?

EDIT: I am not sure why this is so hard for me to grasp. I mean video games do this all the time. Take Tiny Tower for example. You stock your floors and each one of those operations has a delay from when you start the stock, until when that item is stocked and it triggers a HUD that pops up and says, "Floor is now stocked". Meanwhile the whole game stays running for you to do other things. I must be dense because I cannot figure this out.

Community
  • 1
  • 1
Jasmine
  • 15,375
  • 10
  • 30
  • 48

2 Answers2

4

This snippet of code will execute a std::vector of nullary tasks in a separate thread.

typedef std::vector<std::function< void() >> task_list;
typedef std::chrono::high_resolution_clock::duration timing;
typedef std::vector< timing > timing_result;

timing_result do_tasks( task_list list ) {
  timing_result retval;
  for (auto&& task: list) {
    std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
    task();
    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
    retval.push_back( end-start );
  }
  return retval;
}
std::future<timing_result> execute_tasks_in_order_elsewhere( task_list list ) {
  return std::async( std::launch::async, do_tasks, std::move(list) );
}

this should run each of the tasks in series outside the main thread, and return a std::future that contains the timing results.

If you want the timing results in smaller chunks (ie, before they are all ready), you'll have to do more work. I'd start with std::packaged_task and return a std::vector<std::future< timing >> and go from there.

The above code is untested/uncompiled, but shouldn't have any fundamental flaws.

You'll note that the above does not use std::thread. std::thread is a low level tool that you should build tools on top of, not something you should use directly (it is quite fragile due to the requirement that it be joined or detached prior to destruction, among other things).

While std::async is nothing to write home about, it is great for quick-and-dirty multiple threading, where you want to take a serial task and do it "somewhere else". The lack of decent signaling via std::future makes it less than completely general (and is a reason why you might want to write higher level abstractions around std::thread).

Here is one that will run a sequence of tasks with a minimum amount of delay between them:

#include <chrono>
#include <iostream>
#include <vector>
#include <functional>
#include <thread>
#include <future>

typedef std::chrono::high_resolution_clock::duration duration;
typedef std::chrono::high_resolution_clock::time_point time_point;
typedef std::vector<std::pair<duration, std::function< void() >>> delayed_task_list;

void do_delayed_tasks( delayed_task_list list ) {
  time_point start = std::chrono::high_resolution_clock::now();
  time_point last = start;
  for (auto&& task: list) {
    time_point next = last + task.first;
    duration wait_for = next - std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for( wait_for );
    task.second();
    last = next;
  }
}
std::future<void> execute_delayed_tasks_in_order_elsewhere( delayed_task_list list ) {
  return std::async( std::launch::async, do_delayed_tasks, std::move(list) );
}
int main() {
  delayed_task_list meh;
  meh.emplace_back( duration(), []{ std::cout << "hello world\n"; } );
  std::future<void> f = execute_delayed_tasks_in_order_elsewhere( meh );
  f.wait(); // wait for the task list to complete: you can instead store the `future`
}

which should make the helper async thread sleep for (at least as long as) the durations you use before running each task. As written, time taken to execute each task is not counted towards the delays, so if the tasks take longer than the delays, you'll end up with the tasks running with next to no delay between them. Changing that should be easy, if you want to.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Why the need for a universal reference at `for (auto&& task: list)`? – Felix Glas Aug 08 '13 at 21:31
  • 1
    This example is interesting.I supposed when I meant "timer" I meant I want to specific an amount of time, a delay, rather, and at the end of that delay, fire an event/run a function. Which does too but I think this will block program execution as well – Jasmine Aug 08 '13 at 21:32
  • @Snps none. I use `auto&& x:v` to mean "I really don't care, just bind it". – Yakk - Adam Nevraumont Aug 08 '13 at 22:00
  • 1
    @Jason anything done in `do_tasks` is non-blocking in the main thread. You can change its return type to `void` if you don't care about the timing stuff (and strip it out elsewhere). Then add some delays inside it. Note that doing things in another thread is often difficult to do right, and at your level of expertise I would recommend picking up some concurrency 101 stuff. – Yakk - Adam Nevraumont Aug 08 '13 at 22:02
  • I am reviewing the example you posted and what I am seeing is that `list` is a `vector` of functions. So what would be the way to fill that? I have never thought about a vector of functions before. – Jasmine Aug 08 '13 at 22:12
  • `push_back` with a function that takes no arguments? Or a lambda `[]{ code goes here }`? ... if you don't understand `std::function`, then I would advise against using `std::thread` extremely strongly. It would be like learning calculus before you learn the times tables: possible, but why would you bother? – Yakk - Adam Nevraumont Aug 08 '13 at 22:15
  • that makes sense on how to do it. Yes, I figured `push_back` I am not that confused. – Jasmine Aug 08 '13 at 22:18
  • So I am trying to do: `std::vector> list;` `std::function _test = &Broccoli::test;` `list.push_back(_test);` but I am getting: `No matching member function for call to 'push_back'` – Jasmine Aug 08 '13 at 22:51
  • I have: `void Broccoli::test() { std::cout << "testing" << std::endl; }` – Jasmine Aug 08 '13 at 22:52
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35113/discussion-between-yakk-and-jason) – Yakk - Adam Nevraumont Aug 08 '13 at 23:00
  • This is really helpful. You have really shown me some good stuff and it made me want to read my new Bjarne book last night. Thanks for that. – Jasmine Aug 09 '13 at 16:18
  • Can you explain 2 things. 1. I get an error on the task() line that says: `9: Type 'std::__1::pair >, std::__1::function >' does not provide a call operator` – Jasmine Aug 09 '13 at 16:19
  • and can you explain how to add void functions to the `task_list` that are not static? Was that in the chat last night? Thank you again for the time you have taken. Explanations are great and I am learning – Jasmine Aug 09 '13 at 16:20
  • `[=]{ code to run in a std::function}` can be converted into a `std::function` via `push_back`. As we are running in another thread, I copy – Yakk - Adam Nevraumont Aug 09 '13 at 16:53
  • no, I am just using your example, I am not starting another thread – Jasmine Aug 09 '13 at 17:48
  • ah ha, it was the [=] I thought I would be using [void] for some reason. – Jasmine Aug 09 '13 at 17:51
  • I am still trying to sort out this error on the `task()` line that says: `9: Type 'std::__1::pair >, std::__1::function >' does not provide a call operator` – Jasmine Aug 09 '13 at 18:01
  • [running example](http://coliru.stacked-crooked.com/view?id=080faee2b552903d3033240e73266f91-25783dc5e0579471d3e326cae35dc982) with bugs removed. – Yakk - Adam Nevraumont Aug 09 '13 at 18:34
  • this is really making sense. For `return std::async( std::launch::async, do_delayed_tasks, std::move(list) );` for `do_delayed_tasks` Once I change it to my class like: `return std::async( std::launch::async, Broccoli::do_delayed_tasks, std::move(list) );` I get an error about: `54: Reference to non-static member function must be called` and I dont understand why. what is making it static? – Jasmine Aug 09 '13 at 20:29
  • I am trying to really get familiar. Take into consideration this: ` meh.emplace_back(std::thread([](){ std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; }));` what is the mistake here if the error is:`No matching constructor for initialization of 'std::__1::function'` going over what you and I have talked about I thought I did this right. – Jasmine Aug 09 '13 at 22:05
  • @Jason `emplace_back` requires you pass all arguments to a valid constructor of the type, and my `meh` is a pair of both delay (a duration) and a function. I provided a link to completely compiling code with an example run. Second, stop using `std::thread`, `std::thread` is a low level primitive. – Yakk - Adam Nevraumont Aug 09 '13 at 22:15
  • yup, your example runs great. I was just trying to really learn the ins and outs – Jasmine Aug 09 '13 at 22:31
  • I need to accept your answer. Could you show me one more thing? in your example, calling a member function in the lambda? I seem to be getting stuck on it wanting to auto-correct with `()` or use `&` – Jasmine Aug 09 '13 at 22:36
  • If you have a class instance `Bar*bar` whose lifetime you are going to manage manually, you simply do `[bar](){ bar->DoStuff(); }` to create a zero-argument lambda that invokes `bar->DoStuff()`. The first `()` are optional. – Yakk - Adam Nevraumont Aug 09 '13 at 22:37
  • but what if you are filling the `meh` vector in the constructior ff that class, so otherwise creating it elsewhere? So you do `Bar b;` in code and in the constructor for `Bar` you fill the vector? – Jasmine Aug 09 '13 at 22:43
  • I guess you can't do that because of `this` I guess maybe create a member function to do it and do something like `Bar b; bar.xxx();` and in `bar.xxx()` do the filling of the vector and executing the functions in it. – Jasmine Aug 09 '13 at 22:47
  • or I could just fill the vector in the class that creates the `Bar b;` with items like: `meh.emplace_back([bar](){ bar->task1(); } );`, `meh.emplace_back([bar](){ bar->task2(); } );` and execute it. – Jasmine Aug 09 '13 at 22:59
  • Or, actually this in the constructor: `meh.emplace_back([this](){ this->hello1(); } );` – Jasmine Aug 09 '13 at 23:01
  • @Jason Then capture `this`. Be extremely careful of lifetime issues. I'm neither qualified nor prepared to teach you to program a concurrent program from scratch, concurrency isn't easy. – Yakk - Adam Nevraumont Aug 09 '13 at 23:02
  • @Yakk. I found a concurrency book I am going to pick up. I have the new Bjarne book. Do you have a recommendation of a good teaching C++ book. I haven't been up on all the new items since I learned back in 1996-1998 – Jasmine Aug 09 '13 at 23:04
0

Your trouble is understandable, because what you need in order to have timers that don't block your event loop, is an event loop, and C++ doesn't yet have a standard one. You need to use other frameworks (such as Qt, Boost.Asio(?) or non-portable APIs (select(), etc)) to write event loops.

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90