I was trying to do a sample threading program, where the spawning of thread is in a while loop. I DO NOT want to generate multiple number of threads. The while loop should keep on running, irrespective of whether the thread completed execution or not. There is a call to the .get() function of the above thread within the while loop. Again, this has to be called only when the thread executed correctly.
My code is given below:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
int thread(int varJ)
{
std::cout<<"\t\t"<<varJ<<std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return varJ;
}
int main()
{
int varI = 0;
std::future<int> future = std::async(std::launch::async, [](){return 0;});
std::future_status status;
bool flag = false;
while(1)
{
status = future.wait_for(std::chrono::milliseconds(0));
if(status == std::future_status::ready && !flag)
{
future = std::async(std::launch::async, thread, varI);
flag = true;
}
status = future.wait_for(std::chrono::milliseconds(0));
if(status == std::future_status::ready && flag)
{
std::cout << "\t result is " << future.get() << '\n';
future = std::async(std::launch::async, [](){return 0;});
flag = false;
}
varI++;
}
}
The output obtained:
0
result is 0
865
result is 865
1723
result is 1723
2632
result is 2632
3590
result is 3590
4551
result is 4551
5509
result is 5509
The output is as I expected it to be. But is this the way to code or is there a better way?
I am not pleased to use a flag
in between the code, calling future.wait_for
a lot many times and especially using the dummy thread that returns 0.
-
1\$\begingroup\$ What is the goal of the output? \$\endgroup\$Pimgd– Pimgd2016年04月07日 12:13:16 +00:00Commented Apr 7, 2016 at 12:13
-
\$\begingroup\$ There are 2 goals. (1) To find an effective way of writing such a code, my current code seems so crude. (2) To solve issue present here - stackoverflow.com/questions/36467678/…. \$\endgroup\$Anoop K. Prabhu– Anoop K. Prabhu2016年04月07日 12:22:53 +00:00Commented Apr 7, 2016 at 12:22
2 Answers 2
I see some things that may help you improve your code.
Simplify your code using literals
There is nothing intrinsically wrong with std::chrono::milliseconds(0)
but I would prefer to read (and to write) 0ms
instead. This can be done by adding this line to within main
:
using namespace std::literals::chrono_literals;
Use more meaningful variable names
I understand that it's just a toy program, but I really think we can do better than varI
and varJ
! Since it's just a freerunning counter, why not just call it count
?
Understand the threading model
It's important to realize that std::future
was designed to hold not only the future value (or exception) but also a flag for the future_status
. This renders the current code's flag
unnecessary. Here's a more concise rewrite of main
:
int main()
{
using namespace std::literals::chrono_literals;
int count = 0;
std::future<int> future = std::async(std::launch::async, thread, count);
while(1) {
if (future.wait_for(0ms) == std::future_status::ready) {
std::cout << "\t result is " << future.get() << '\n';
future = std::async(std::launch::async, thread, count);
}
++count;
}
}
Beware of sharing unsynchronized variables
Right now, both the thread running main
and the thread running the function write to std::cout
. The way the code is currently structured (both in the original and in the rewrite above), they do not contend for that same variable but they could because there currently is no synchronization. The effect is that the output of the two threads could easily be interleaved. The easiest way to prevent this is to only access cout
from a single thread.
-
\$\begingroup\$
0ms
, very cool \$\endgroup\$Pimgd– Pimgd2016年04月07日 12:29:53 +00:00Commented Apr 7, 2016 at 12:29
std::future
is useful primarily when you can spawn off some work to be done asynchronously, continue with some more work in the thread that spawned it off, and sometime later get one or more results from the work it spawned off.
For this case, I would think in somewhat different terms (even if they're pretty much what you've said you don't want). Specifically, I'd set up what some might call a software pipeline. The "lead" thread just pushes some tasks into a queue. A pool of some number of threads (one or more) executes those tasks. Those push results to another queue. A final thread just pulls results from that queue, and (in this case) prints out the values they returned.
Code for a thread-safe queue is, unfortunately, quite a bit longer and uglier than I'd like--the C++ committee decided to emulate POSIX' mistake, and provide only condition variable for this sort of signaling, which leads to code that's long, ugly, and difficult to understand compared to (for one obvious example) using counted semaphores instead.
I also think that particular design is...kind of flawed. It has a try_pop and a wait_and_pop. Unfortunately, neither is what you really usually want: one pops an item if its available, but fails if an item isn't available immediately. The other goes to the opposite extreme, and waits forever, and just freezes permanently if an item doesn't become available.
At least in my experience, you usually want something in the middle: an ability to set a timeout, and have it wait up to that long to get an input. Then, if that times out, it returns false
(or something on that order) to signal failure.
Fortunately, that's fairly easy to add. Once you have it, the code basically becomes:
main thread:
concurrent_queue<int> input(timeout=10ms);
concurrent_queue<int> output(timeout=200ms);
create_processing_thread(input, output);
for (i=0 to N)
input_queue.push(i);
int j;
while (output.pop(j))
print(j)
...and the processing thread is something like this:
processing thread:
while (input.pop(value))
sleep(100)
output.push(value);
Note the nearly complete lack of thread synchronization: that's all handled by the queues. The rest of the code just pushes tasks into a queue, and retrieves results from a queue. The (minimal) synchronization used is handled entirely inside the queues.
Also note that in this case we may have created only a single processing thread, but if we want more work to be done more quickly, we can scale up the number of threads without changing much of anything else.
Explore related questions
See similar questions with these tags.