6

I have a std::vector<...> that is shared in two threads.

Both of them make calls to vec->size();

Can this be a source of race conditions? I'm hoping not since vec->size() is const.

Thanks!

Paul R
  • 208,748
  • 37
  • 389
  • 560
anon
  • 41,035
  • 53
  • 197
  • 293
  • 1
    FYI: The `size()` on gcc 4.2 on Mac OS X is implemented as `size_type size() const { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }`. – kennytm Mar 04 '10 at 08:16
  • Where did you find this? Enlighten me. – anon Mar 04 '10 at 08:17
  • Standard headers are under "/usr/include/c++/*", start from the one you actually include '' and keep reading the included files until you find what you look for '' is a common header for g++ to store the actual vector implementation (with '' being the 'bool' specialization, and some other headers used for utility functions) – David Rodríguez - dribeas Mar 04 '10 at 08:34

2 Answers2

5

If you are calling ONLY vec->size() you are safe. But this is somehow difficult to believe. As soon you call any changing method, such as push_back a race can cause to get the wrong size.

Drakosha
  • 11,925
  • 4
  • 39
  • 52
  • 2
    Not so hard to believe since... I suppose someone can check the vector.size() and *if* is not empty, *then* can do a mutex lock/unlock. – Vassilis Nov 04 '18 at 16:59
2

Probably not. The problem isn't really in vec->size(), it's in all the other functions as well.

Consider this: vector::size() is typically calculated directly from members, e.g. .end - .begin. Now what happens with a push_back on one thread? It affects the size, obviously, via the members. It changes memory. But there is no memory barrier. Other threads on other cores will simply see the old memory. As a result, when they call size(), it will be calculated using the old values.

An obvious exception is when the vector doesn't change size after the creation of the threads. The threads will never have outdated information.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I completely agree with this. When you are writing multi-threaded code you should aim to make your operations atomic. Even _if_ this operation on its own is safe; if you are using mutexs or critical sections for other operations you must use them for this too. If you are worried about performance use a readers writer lock to enable multiple readers to size() but only a singe writer. – iain Mar 04 '10 at 12:06
  • Actually size() doesn't access the members. It can't crash and the worse thing that can happen is an outdated result. See my answer. In some cases it's ok to have an outdated value, like for displaying a progress bar representing the size of a container to a user. It's fine because you don't want to iterate on the container anyway and if the value is outdated, it will be correct on the next refresh of the display. – Aurélien Mar 28 '13 at 15:42
  • 2
    @Aurelien: What you are claiming directly contradicts the first comment from KennyTM, which clearly shows that gcc 4.2 accesses the `_M_finish` and `_M_start` members. That can produce a _negative_ size if another thread changes reallocates the vector, and you subtract the old `_M_start` from the new `_M_finish`. Cast that negative result back to `size_type` and you have a value that is nonsensically big. And yes, drawing a progress bar 4 billion pixels wide may cause some troubles. – MSalters Mar 29 '13 at 07:46
  • Yes, you are right. I obviously misunderstood that sentence in the documentation: "No contained elements are accessed: concurrently accessing or modifying them is safe." It probably only means that size() is thread-safe against a concurrent modification of the elements already in the container. It doesn't mean that it is thread-safe against modifying the amount of elements. Thanks, I'll remove my answer. – Aurélien Mar 29 '13 at 17:47