4

I want to see if I am forced to use atomic integers.

I have a loop that looks similar to this:

struct loop {
  volatile int loop_variable;
  volatile int limit;
}

for (int i = loop.loop_variable ; i < loop.limit ; loop.loop_variable++) {

}

Then another thead does this:

loops.loop_variable = loops.limit;

And issues a memory barrier.

Is this multithreaded safe?

The assembly where there is a data race is between these lines:

// loop.loop_variable = loop.limit;
movl    4+loop.0(%rip), %eax                                                                                                                                                             
movl    %eax, loop.0(%rip)  

And

    // for (int i = loop.loop_variable ; i < loop.limit ; loop.loop_variable++)
    movl    loop.0(%rip), %eax                                                                                                                                                               
    movl    %eax, -4(%rbp)                                                                                                                                                                   
    jmp .L2                                                                                                                                                                                  
.L3:                                                                                                                                                                                         
    movl    loop.0(%rip), %eax                                                                                                                                                               
    addl    $1, %eax                                                                                                                                                                         
    movl    %eax, loop.0(%rip)                                                                                                                                                               
.L2:                                                                                                                                                                                         
    movl    loop.0(%rip), %eax                                                                                                                                                               
    cmpl    $99999, %eax                                                                                                                                                                     
    jle .L3                                                                                                                                                                                  
    movl    $0, %eax  

There might be a data race between

movl    loop.0(%rip), %eax                                                                                                                                                               
        addl    $1, %eax                                                                                                                                                                         
        movl    %eax, loop.0(%rip)

Since it's three instructions to increment the loop_variable. But only one to overwrite the loop variable to the limit.

Samuel Squire
  • 127
  • 3
  • 13
  • 2
    Related: [Why is volatile not considered useful in multithreaded C or C++ programming?](https://stackoverflow.com/q/2484980/2402272) – John Bollinger Jan 25 '23 at 15:13
  • 1
    You have a loop that looks at a structure called `loop` (presumably of type `struct loop`?), and then some code that looks at `loops[0]`. Are those meant to be the same? (If they're not, the answer may be somewhat easier ;-) ) – psmears Jan 25 '23 at 15:16
  • 3
    Volatile, memory barriers and race condition protection are 3 different things used for 3 different purposes. If you normally have 3 tools: a saw, a screwdriver and a hammer, then you still can't saw unless you use a saw. "But I have both a screwdriver AND a hammer, why can't I saw?" Because you can't saw with those tools and nobody claimed that you could either. – Lundin Jan 25 '23 at 15:37
  • Sorry psmears! I was adapting code from memory. I have changed it. – Samuel Squire Jan 25 '23 at 15:49
  • 1
    "I want to see if I am forced to use atomic integers." Are you writing them in one thread, while reading or writing them in another, without a lock or other synchronization to ensure that those operations do not happen "at the same time"? Yes, you are, so the answer is yes, you must use atomic types. Memory barriers do not have any impact on when operations happen with respect to other threads; only on when operations within *this* thread become visible to others. – Nate Eldredge Jan 25 '23 at 15:51
  • 1
    Importantly, `volatile` does *not* exempt you from the data race rules. Concurrently writing/reading a `volatile` variable is just as much a data race as without `volatile`, and the behavior is likewise undefined. Old code, before `` existed, would use `volatile` because it was the closest thing available, and it would work most of the time if you knew a lot about your compiler's optimizations. It's not appropriate in the modern era. – Nate Eldredge Jan 25 '23 at 15:52
  • @NateEldredge Even then, atomic values don't enforce any required consistency between *different* atomic values. Which is why mutexes, semaphores, and other multithread synchronization types exist... – Andrew Henle Jan 25 '23 at 16:19

1 Answers1

1

Is this multithreaded safe?

No.

Given

loops[0].loop_variable = loops[0].limit;
< memory barrier >

in one thread, that memory barrier won't prevent int i = loop.loop_variable from reading an indeterminate value or loop.loop_variable++ producing nonsense results in another thread. Other threads can still potentially "see" the change to loops[0].loop_variable. Or parts of the change.

A memory barrier just imposes consistency afterwards - it doesn't do a thing beforehand.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • What if you put the memory barrier at the end of the loop iteration? And just hope that the loop actually ends and doesn't data race more than once in a row. – Samuel Squire Jan 25 '23 at 16:00
  • @SamuelSquire Memory barriers do not provide synchronization between threads. All they do is ensure all threads "see" a consistent view of memory **after** they're executed. – Andrew Henle Jan 25 '23 at 16:16