Necessary to use a Mutex for a simple global flag?

Just starting out? Need help? Post your questions and find answers here.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Necessary to use a Mutex for a simple global flag?

Post by Oso »

I probably ought to know the answer to this, but I wonder if I've been over-engineering this pricinple in threads, because most of the time I use a global map across threads, where the mutex is absolutely essential. But what about a simple global flag? Should I still use a mutex in threads that look at the flag, or set the flag?

Code: Select all

Global ShutdownInd.b                                                ; Global shutdown flag, can be checked by threads
Global ShutdownMutex = CreateMutex()                                ; Global shutdown mutex
.
.
.
    ; ** Thread :
    LockMutex(ShutdownMutex)                                        ; <----- Is this necessary?
    ShutdownInd.b = #True                                           ; Set global flag to close down server service
    UnlockMutex(ShutdownMutex)                                      ; <----- Is this necessary?
freak
PureBasic Team
PureBasic Team
Posts: 5948
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Re: Necessary to use a Mutex for a simple global flag?

Post by freak »

If the operation is "atomic" then there is no mutex needed. Atomic means a single read or write up to the size of the processor registers (up to integer or pointer size).

So:

Code: Select all

ShutdownInd.b = #True  ; Atomic

x = ShutdownInd.b      ; Atomic

Something.i = 123      ; Atomic

Something.i + 5        ; Not Atomic because there is a read, addition and then a separate write back to the variable!

SomethingElse.q = 999  ; Atomic only on 64bit platforms. Not atomic on 32bit ones!

SomeString$ = "foo"    ; Never atomic

SomeArray.i(x, y) = 5  ; Atomic (but nobody may ReDim the array at the same time)

z = SomeArray.i(x, y)  ; Atomic (as long as the array size is not modified)

; List and Map operations: Never atomic
As soon as you want to do more than one thing to a variable you need a mutex to protect that.
quidquid Latine dictum sit altum videtur
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Necessary to use a Mutex for a simple global flag?

Post by Oso »

freak wrote: Sat Nov 04, 2023 1:01 pm If the operation is "atomic" then there is no mutex needed. Atomic means a single read or write up to the size of the processor registers (up to integer or pointer size).
Many thanks indeed, freak, those examples are great and it's good to get this answered.
User avatar
STARGÅTE
Addict
Addict
Posts: 2260
Joined: Thu Jan 10, 2008 1:30 pm
Location: Germany, Glienicke
Contact:

Re: Necessary to use a Mutex for a simple global flag?

Post by STARGÅTE »

freak wrote: Sat Nov 04, 2023 1:01 pm If the operation is "atomic" then there is no mutex needed. Atomic means a single read or write up to the size of the processor registers (up to integer or pointer size).

So:

Code: Select all

ShutdownInd.b = #True  ; Atomic

x = ShutdownInd.b      ; Atomic

Something.i = 123      ; Atomic

Something.i + 5        ; Not Atomic because there is a read, addition and then a separate write back to the variable!

SomethingElse.q = 999  ; Atomic only on 64bit platforms. Not atomic on 32bit ones!

SomeString$ = "foo"    ; Never atomic

SomeArray.i(x, y) = 5  ; Atomic (but nobody may ReDim the array at the same time)

z = SomeArray.i(x, y)  ; Atomic (as long as the array size is not modified)

; List and Map operations: Never atomic
As soon as you want to do more than one thing to a variable you need a mutex to protect that.
Very clear example, thanks freak. It should be added to the documentation for thread/mutex.
Especially the example for non-atomic quads for x86 is very important.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Lizard - Script language for symbolic calculations and moreTypeface - Sprite-based font include/module
User avatar
spikey
Enthusiast
Enthusiast
Posts: 778
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: Necessary to use a Mutex for a simple global flag?

Post by spikey »

STARGÅTE wrote: Sat Nov 04, 2023 6:17 pm It should be added to the documentation for thread/mutex.
+1
juergenkulow
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 25, 2019 10:18 am

Re: Necessary to use a Mutex for a simple global flag?

Post by juergenkulow »

What exactly should be where in the library thread, Documentation/English/Thread.txt?
User avatar
spikey
Enthusiast
Enthusiast
Posts: 778
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: Necessary to use a Mutex for a simple global flag?

Post by spikey »

juergenkulow wrote: Sat Nov 04, 2023 7:48 pm What exactly should be where?
A remarks section in the parent 'Thread' item maybe. There's already a note to the effect of needing to use a mutex but it isn't emphasised particularly and it could do with being made more prominent because this is essential for bug-free multi-threaded programming.
juergenkulow
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 25, 2019 10:18 am

Re: Necessary to use a Mutex for a simple global flag?

Post by juergenkulow »

That would be after line 41?
User avatar
spikey
Enthusiast
Enthusiast
Posts: 778
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: Necessary to use a Mutex for a simple global flag?

Post by spikey »

I was thinking about reworking from line 37 something like this. (I haven't tested it though because I don't have time to play with docmaker today).

Code: Select all

...threadmode should only be used when there is a real need for it.
@LineBreak
@LineBreak
@Remarks
  Using the @ReferenceLink "threaded" "Threaded" keyword it's possible to create thread-based
  persistent objects (variables, arrays, lists, maps).
@LineBreak
@LineBreak
@OS Windows
  Note: Don't use DirectX inside threads (MS Windows limitation)! If you need to display
  graphics in threads use @LibraryLink "image" "Images" and
  @LibraryLink "2ddrawing" "2DDrawing" instead.
@LineBreak
@LineBreak
@EndOS

@Section Using Shared Resources

  Shared resources (memory, variables, files, etc) need to be controlled carefully in threaded programs because it is possible that you can have concurrent access to them in multiple threads. You need
  to manually ensure that you do not run into trouble because of this.

  Actions which can be performed within a single register and in a single instruction are referred to as 'atomic' operations.  Actions which require operations larger than the size of a register or multiple operations are 'non-atomic'.
  Atomic operations on shared resources may not need to be protected, if no other non-atomic operations will occur, but non-atomic operations should always be protected using the Mutex functions in this library to control access.

@Section Atomic And Non-Atomic Operations
@Code
ByteOne.b = #True          ; Atomic.
ByteOne.b = ByteTwo.b      ; Atomic.
ByteOne.b + 5              ; Non-atomic because there is a read, addition and then a separate write back to the variable.

IntegerOne.i = 123         ; Atomic.
IntegerOne.i + 5           ; Non-atomic because there is a read, addition and then a separate write back to the variable.

QuadOne.q = 999            ; Atomic only on 64-bit platforms. Non-atomic on 32-bit ones.
QuadOne.q + 5              ; Non-atomic.

StringOne$ = "foo"         ; String operations are always non-atomic.

SomeArray.i(x, y) = 5      ; Atomic (providing the array size is not modified at the same time).
z = SomeArray.i(x, y)      ; Atomic (providing the array size is not modified at the same time).
ReDim SomeArray.i(10)      ; Non-atomic.

AddElement(SomeList())     ; List operations are always non-atomic.
AddMapElement(SomeMap())   ; Map operations are always non-atomic.
@EndCode
...
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Necessary to use a Mutex for a simple global flag?

Post by Oso »

Speaking from own experience, having attempted to find the answer in the documentation, I would have thought line 100 in the documentation, is an appropriate place. That section already touches on the subject of what the mutex is used for, so it would seem good to follow on from that :
The main objective of the mutex functions is thread synchronization. They do not create too much overhead... etc.
The question seems to be, in which case is the mutex needed? In my case, I've been over-engineering development by protecting global variable access when it wasn't always necessary. The mutex section would seem to be a reasonable place to define that.

It would also be useful to add further that, where threads are concerned, any map, list or array — given that these are shared resources — is generally required to be accessed in conjunction with a mutex. It might add that since their current element is not set per-thread, but per-process, a mutex offers a solution. The example code doesn't clarify this, as it gives instead a more simplified case of using a mutex to ensure threads execute in sequence.
freak
PureBasic Team
PureBasic Team
Posts: 5948
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Re: Necessary to use a Mutex for a simple global flag?

Post by freak »

Oso wrote: Sun Nov 05, 2023 5:52 pm The question seems to be, in which case is the mutex needed? In my case, I've been over-engineering development by protecting global variable access when it wasn't always necessary. The mutex section would seem to be a reasonable place to define that.
But your "over-engineered" solution was still correct so it did not really hurt. Correctness is very important in threads programming because things can seem to work fine for a long time until they break in rare and hard to reproduce ways. So when in doubt adding a mutex that is technically not needed is still the preferable outcome to missing one that would have been required.

This is why I am a bit hesitant to document this in too much detail. It encourages people with a limited understanding of threads and the memory semantics of the CPU to try to "optimize" away mutexes and break their code. For a beginner using a mutex is the recommended solution to synchronize global data even if technically not always needed.
quidquid Latine dictum sit altum videtur
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Necessary to use a Mutex for a simple global flag?

Post by Oso »

freak wrote: Mon Nov 06, 2023 6:41 pm But your "over-engineered" solution was still correct so it did not really hurt. Correctness is very important in threads programming because things can seem to work fine for a long time until they break in rare and hard to reproduce ways. So when in doubt adding a mutex that is technically not needed is still the preferable outcome to missing one that would have been required.

This is why I am a bit hesitant to document this in too much detail. It encourages people with a limited understanding of threads and the memory semantics of the CPU to try to "optimize" away mutexes and break their code. For a beginner using a mutex is the recommended solution to synchronize global data even if technically not always needed.
I think those are fair points — threads require a different mindset in programming, like some other forms of timing-dependent code and perhaps here on the forum is the best way to gain that. Certainly there's quite a learning period involved with them, not so much with the syntax, as that's easy to remember, but more so with regard to what's happening internally — and might happen (I find it helps to try to visualise them working in my mind).
User avatar
idle
Always Here
Always Here
Posts: 6044
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Necessary to use a Mutex for a simple global flag?

Post by idle »

it's OK to assign a global variable in a thread without a mutex as in it won't crash but there is still a potential problem from out of order execution and memory reordering which causes the aba problem.
So there are still cases where it's not safe, for instance a ring buffer which is fine if it runs on a single cpu but not on a multicore system. X86/x64 has mfence, lfence, sfence and in c same can be done via macros or with synchronize instrinsics. While a fence takes 100s of cycles it's a lot better than 1000s of cycles being lost to a critical section aka pb mutex in highly contentious conditions.

Here's a good explanation
https://preshing.com/20120515/memory-re ... n-the-act/

Code: Select all

Macro sfence
  CompilerIf #PB_Compiler_Backend = #PB_Backend_Asm  
    !sfence 
  CompilerElse 
    CompilerIf #PB_Compiler_Processor = #PB_Processor_Arm32 Or #PB_Compiler_Processor = #PB_Processor_Arm64
      !__sync_synchronize();
    CompilerElse   
      !__asm__("sfence" ::: "memory");   
    CompilerEndIf   
  CompilerEndIf   
EndMacro 

Macro lfence 
  CompilerIf #PB_Compiler_Backend = #PB_Backend_Asm   
    !lfence
  CompilerElse
    CompilerIf #PB_Compiler_Processor = #PB_Processor_Arm32 Or #PB_Compiler_Processor = #PB_Processor_Arm64 
      !__sync_synchronize();
    CompilerElse  
      !__asm__("lfence" ::: "memory"); 
    CompilerEndIf   
  CompilerEndIf    
EndMacro 

Macro mfence 
  CompilerIf #PB_Compiler_Backend = #PB_Backend_Asm   
    !mfence
  CompilerElse
    CompilerIf #PB_Compiler_Processor = #PB_Processor_Arm32 Or #PB_Compiler_Processor = #PB_Processor_Arm64 
      !__sync_synchronize();
    CompilerElse  
      !__asm__("mfence" ::: "memory"); 
    CompilerEndIf   
  CompilerEndIf    
EndMacro    
User avatar
Caronte3D
Addict
Addict
Posts: 1377
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Necessary to use a Mutex for a simple global flag?

Post by Caronte3D »

Oso, I think it's not worth saving code on these things, the risk of random errors (very difficult to find) is huge, it is always better to use the necersary mutex, semaphores or your own algorithms when using threads.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Necessary to use a Mutex for a simple global flag?

Post by Oso »

idle wrote: Mon Nov 06, 2023 8:16 pm it's OK to assign a global variable in a thread without a mutex as in it won't crash but there is still a potential problem from out of order execution and memory reordering which causes the aba problem.
This is still something that concerns me with this sort of software development — the reordering of memory.

Does this mean that if I do this...

Code: Select all

*Command.Cparm  = AllocateStructure(Cparm)
... then the operating system can later change *Command's memory address, without my knowledge?
Post Reply