0

I'm learning the C++11 features and wrote some code along the following lines

#include <vector>
#include <thread>
using std::thread;
using std::vector;

double Computation(int arg)
{
    // some long-running computation
    return 42.0;
}

double ConcurrentComputations()
{
    const int N = 8; // number of threads
    vector<thread> thr;
    vector<double> res(N);
    const int arg = 123456; // something or other
    // Kick off threads which dump their results into res
    for(int i=0; i<N; ++i)
        thr.push_back(thread ([&res, i, arg]()
                {  res[i] =  Computation(arg); } ));
    // Wait for them to finish and get results
    double sum = 0;
    for(int i=0; i<N; ++i) {
        thr[i].join();
        sum += res[i];
    }
    return sum;
}

Looking at it again in the cold light of day, I don't think I really should have been taking a reference to a vector in the lambda function and dumping data into it. I was thinking of the vector as a regular array and relying on the implementation of operator[] to simply add i to &res.front() (maybe I should have captured &res.front() instead, and now that I've read further in Stroustrup 4ed I can see I should maybe use futures and promises).

Nevertheless, my question is, is it reasonable to think that in practice I can get away with the code that I wrote?

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
TooTone
  • 7,129
  • 5
  • 34
  • 60
  • 1
    It's safe as you reserve the vector elements before assigning values to them. If you did a `ref.emplace_back(Computation(arg));` after the computation completes, that would have been a problem. Plus the `res` reference is safe as it does not go out of scope before joining. – CodeAngry Feb 27 '14 at 00:52
  • @Yakk Thanks. I'd welcome some tidying up if this is a duplicate. But I don't think it duplicates that _question_. The 1st line reads "I am developing a multi threaded application, each thread will **read** (there will be **no modifying** of structures) from a group of maps and vectors." (my bold). I may be duplicating the _answer_: its last line makes it clear it applies to writing as well as reading, viz "Thus in C++11 it is allowed not only to read objects , but also **allow concurrent modification** of its different objects(but not container!), with exception for vector " (my bold). – TooTone Feb 27 '14 at 09:50

2 Answers2

5

You're code is actually fine! (Code is generally broken by default when threads are mixed in, but not in this case.)

The declaration vector<double> res(N); will initialize the array with enough space for all the results, so the vector will never be resized in the loop.

Each thread only writes to a different element of the vector, and there's implicit memory barriers in the thread constructor and join() method that keep ordering as you'd expect.

Now, as to whether this is actually supported by the standard -- hmm, probably not (most references I find to thread-safety regarding std::vector only give guarantees for reads, not writes). Capturing the front wouldn't help either, because you're still doing a write to an element of the vector either way.

(Note that I've personally used this exact pattern successfully across platforms without encountering any problems.)

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • Thankyou for your prompt and clear reply. Also, that's what I wanted to hear: I had to put something together to a deadline, and was wondering how deep a hole I had dug myself... :) – TooTone Feb 26 '14 at 23:38
3

I believe your code is (almost) legal, with well defined behavior (see below for the explanation of the "almost" part).

The important parts of the standard are these:

17.6.5.9/5 A C++ standard library function shall not access objects indirectly accessible via its arguments or via elements of its container arguments except by invoking functions required by its specification on those container elements.

17.6.5.9/6 Operations on iterators obtained by calling a standard library container or string member function may access the underlying container, but shall not modify it.

res[i] is equivalent to *(res.begin() + i) (Table 101 in 23.2.3) This is the only hitch: I can't prove from the text of the standard that res.begin() doesn't modify the vector. If we postulate a reasonable implementation that doesn't do something egregious like this, then the rest is smooth sailing: a call to begin is followed by operator+ and operator* on the resulting iterator, both of which may only access the container but not modify it. Concurrent accesses to a shared object are OK, they don't cause data races.

res[i] returns a double&, and then an assignment to this double object is performed. This is a modification - but no two threads modify the same object, so no races here, either.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • +1, though I think you mean "concurrent reads" and not "concurrent accesses" (which encompass writes as well). The `begin()` technicality could be worked around by passing a reference to the it to each thread (instead of each thread calling it separately), but like you say, a reasonable implementation won't modify the object in `begin()` anyway :-) – Cameron Feb 27 '14 at 01:19
  • 1
    @Cameron: come to think of it, one can pass `double&` or `double*` to an individual element down to the thread, and render the issue moot entirely. – Igor Tandetnik Feb 27 '14 at 01:31
  • 1
    see http://stackoverflow.com/questions/16130494/are-stdmap-and-stdvector-thread-safe for the rest. TL;DR: your code is safe. – Yakk - Adam Nevraumont Feb 27 '14 at 02:10