0

Consider the following code:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <future>

std::mutex mutex;

int generate()
{
    static int id = 0;
    std::lock_guard<std::mutex> lock(mutex);
    id++;
    std::cout << id << '\n';
    return id;
}

int main()
{
    std::vector<std::future<int>> vec;
    std::vector<int> result;
    for(int i = 0; i < 10; ++i)
    {
        vec.push_back(std::async(std::launch::async,generate));
    }
    for(int i = 0; i < 10; ++i)
    {
        result.push_back(vec[i].get());
    }
    std::cout << "\n result:";
    for(const auto res : result)
    {
        std::cout << res << " ";
    }
}

Which produces following output:

1
2
3
4
5
6
7
8
9
10

 result:1 2 3 6 4 5 7 8 9 10

As you can see, the output inside generate() function has correct (from 1 to 10) order. But when iterating over the result vector I'm getting different order each run. Why is this happening and how can I synchronize such code?

b2mb0e
  • 31
  • 5
  • 2
    Why do you expect the results to be ordered? A mutex only guarantees safety (mutual exclusion), but the thread for (say) `v[4]` could still run before `v[3]`. – chi Sep 12 '22 at 13:03
  • 1
    you have no control over the order in which the calls to `generate` are actually made by `std::async` (unless you use `std::launch::deferred` but that somewhat defeats the point of using `std::async`) – 463035818_is_not_an_ai Sep 12 '22 at 13:03
  • 2
    You don't really have any control over the order in which the threads are executed. Right now you have a race for acquiring the lock. If you want threads to execute in a specific order, you need to create some other synchronization. – Some programmer dude Sep 12 '22 at 13:04
  • btw the output you posted is in same order. You should post the output the unexpected output not only the expected one – 463035818_is_not_an_ai Sep 12 '22 at 13:04
  • 4
    Why do you believe that the relative order in which all execution threads lock the mutex, increment `id` must be the same order in which the execution threads get created? – Sam Varshavchik Sep 12 '22 at 13:04
  • 2
    `get()` doesn't say "start the thread and wait for the result"; all it says is "wait for the result". The order of execution of the threads is determined by who gets the mutex, and that is not related to the order that you create the threads in. – Pete Becker Sep 12 '22 at 13:06
  • Thanks everyone for the answers. I was not expecting that `v[4]` could run before `v[3]` – b2mb0e Sep 12 '22 at 13:13
  • 2
    I'm struggling to understand this question. `async` literally means "not synchronized". Why start 10 tasks unsynchronized if you want them to be synchronized? – Drew Dormann Sep 12 '22 at 13:13
  • 1
    Following the comments above, in your specific result example, it seems like the thread in the 5th "slot" was running before the one in the 4th "slot". Note that the numbers printed by `generate` will always be 1,2,3...,n but it doesn't mean the threads are executed according to their order in the "slots". – wohlstad Sep 12 '22 at 13:13
  • 1
    Why multiple threads at all if you wish to maintain the order of execution? – Marek R Sep 12 '22 at 13:29
  • 1
    Note that when multiple threads are waiting for mutex release and mutex is released order of resumed threads is unspecified. – Marek R Sep 12 '22 at 13:31
  • what is the actual aim? It is difficult to provide a concise answer because the simple fix is no threads, no async. Simply `result.push_back(i);` will produce the expected output. But I suppose there was a reason to use `std::async` in the first place. What was it? – 463035818_is_not_an_ai Sep 12 '22 at 13:33
  • Here is [improved version of MCVE](https://godbolt.org/z/xfGcbnGdd) – Marek R Sep 12 '22 at 13:49

2 Answers2

1

What you're seeing in the final output is the order in which the threads produced their results.

Consider these three threads as a simplified version:

          +- -+   +- -+    +- -+
Thread    | 1 |   | 2 |    | 3 |
          +- -+   +- -+    +- -+
Result    |   |   |   |    |   |
          +- -+   +- -+    +- -+

Now, no matter which order these execute and acquire the mutex in, the output will be

1 2 3

because id is static, and the first "locker" increments it and prints "1", then the second increments it and prints "2", and so on.

Let's assume that Thread 3 gets the mutex first, then 1, then 2.

That leads to this situation:

          +- -+   +- -+    +- -+
Thread    | 1 |   | 2 |    | 3 |
          +- -+   +- -+    +- -+
Result    | 2 |   | 3 |    | 1 |
          +- -+   +- -+    +- -+

And when you print the results in "vector order", you will see

2 3 1

If you want threads to execute in some predetermined order, you need to synchronize them further.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
-2

If you resolve the future right away, then it will be in sequence. For example:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <future>

std::mutex mutex;

int generate()
{
    static int id = 0;
    std::lock_guard<std::mutex> lock(mutex);
    id++;
    std::cout << id << '\n';
    return id;
}

int main()
{
    std::vector<std::future<int>> vec;
    std::vector<int> result;
    for(int i = 0; i < 10; ++i)
    {
        int result = std::async(std::launch::async,generate).get();
        result.push_back(result);
    }
    std::cout << "\n result:";
    for(const auto res : result)
    {
        std::cout << res << " ";
    }
}
daparic
  • 3,794
  • 2
  • 36
  • 38