6

I have a buffer using a std list container.

A worker is pushing elements on one side, and another thread is popping from the other side. Both these threads are using a mutex before accessing the container.

As a way to see the performance, I need to query the container for its size. But querying for size using a mutex seem like overkill, if not necessary.

Question is, is it necessary?

Documentation says on calling size() (under section Data Races: No contained elements are accessed: concurrently accessing or modifying them is safe.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Totte Karlsson
  • 1,261
  • 1
  • 20
  • 55
  • 5
    Unless explicitly specified as thread-safe, treat everything as *not* thread-safe. – Some programmer dude Sep 20 '19 at 20:35
  • 1
    `vector`, for example, often computes size based on subtracting two pointers. Lot of room in `a-b` for an interruption and it would really suck if the pointers got swapped out by a resize part way through. – user4581301 Sep 20 '19 at 20:43
  • Either way, you would still have a race condition. The size could have changed after you checked. (Anyway you can't do that.) – curiousguy Sep 20 '19 at 21:14
  • Whats the "either" way? And well, the size is supposed to change after checking. That is why I need to keep checking.. ! – Totte Karlsson Sep 20 '19 at 21:18
  • I have emphasized in my answer below that the data you need to focus on here is the list **container** since this is what needs to be protected by mutual inclusion. Your edit refers to the **contained elements** which as I have explained in the comments below is a different kind of data. – nielsen Sep 20 '19 at 21:21
  • @TotteKarlsson Please "at" me if you want a reply. Either way means that even if it wasn't UB (undefined behavior), because you have no locking, the value of size could have changed since you checked. With a lock you can check a value and use the result knowing it's still valid. – curiousguy Sep 20 '19 at 21:25
  • @TotteKarlsson The problem is that the size could be changing **while** you are checking it and reading it in an intermediate state could give you an undefined result (different from both the value before and after the change). Btw., race conditions are some of the hardest problems to troubleshoot because a program can be working 99.99% of the time and suddenly fail for no apparent reason. – nielsen Sep 20 '19 at 21:33
  • You may also benefit from taking a look here: https://stackoverflow.com/questions/15278343/c11-thread-safe-queue – nielsen Sep 20 '19 at 21:40

2 Answers2

6

Question is, is it necessary?

Yes. You could be adding an element into the list while querying it's size and that is undefined behavior.

The rule is if you more than one thread accessing a shared object, and at least one of them writes to said object, you must have synchronization. If you do not you have a data race and that is undefined behavior.


Per your edit:

No contained elements are accessed: concurrently accessing or modifying them is safe. means that the elements of the list are not accessed or mutated. That means you can call size() and not worry about it modifying any element in the list. Right before that it has The container is accessed. It is that access that is not thread safe. If you were adding an element to the list when you called size the the value you get is undefined.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

The short answer is: "yes".

To determine the need for mutual exclusion, one needs to look at the "Data races" specifications of the functions involved. In this case, std::list push/pop functions specifications say that the container is modified. While the size function specification says that the container is accessed.

Generally, whenever some data is accessed or modified by more than one thread and modified by at least one of the threads, then all access and modifications must be protected by mutual exclusion.

nielsen
  • 5,641
  • 10
  • 27
  • @TotteKarlsson The "contained elements" refers to the elements contained in the container which is a different category of data than the container itself. Think of the list (1, 2, 3). The list container is the structure that keeps the list together, i.e. in this textual case, the parenthesis and commas. The list elements are the numbers. So while asking for the list size, it would be ok to modify an element (e.g. change "2" to "4"). Since the "size" operation does not involve the contents of the elements, but only the list container. – nielsen Sep 20 '19 at 21:15