none
Unnecessary lock in this case? RRS feed

  • Question

  • Hello,

    I have a question concerning the locking of a data type in a multi-threaded environment.

    Suppose I have a CLS data type (bool in my case) which is accessed by one writer and one reader thread. Let us say that this variable starts out as "true" and somewhen the writer thread sets it to "false". The reader thread accesses this variable the whole time and checks for this "false" value. Nevertheless it does not happen that there is more than one change to this variable.

    To summarize:
    A variable "X" starts out with value "A". It is continuously accessed by thread "R" which reads its value. One and only one time the thread "W" changes the value of "X" from "A" to "B". Somewhen later thread "R" reads again and act differently because of the changed value.

    My question:
    Is it necessary that I lock accesses to the variable ? I think I do not but since I am more or less a beginner in multi-threaded programming I check twice before entering the frequently mentioned "multi-thread debugger hell" ;).

    Thanks in beforehand for your answers and effort
    Best regards
    Mark Ritter

    PS: If you need more information on my setting just ask.

    Wednesday, August 20, 2008 1:54 PM

Answers

  • There are two issues at stake.  First, is the variable that gets updated atomic.  In other words, is there any danger that the other thread will read a partially updated value.  On Intel/AMD architectures, 32-bit variable updates are atomic.  But 64-bit values, like the "long" C# type are not.  Another thread can potentially read the new value of the lower 32 bits but the old value of the upper 32 bits, generating a nonsense value.  A lock will be required for them.

    The second issue is far more troubling and will be an issue when the machine has a CPU with multiple cores.  Like any machine sold in the past several years.  Each CPU core has its own cache, a "buffer" if you will between the core and system memory.  Updates to variables made by one thread (one core) are not visible to another thread (another core) until that core's cache gets refreshed.  Which means that your second thread will quite likely *not* read the value written by your first thread.  It will read a stale value instead.

    Fixing this problem requires implicitly writing code to force the cache to be refreshed.  Thread.MemoryBarrier() was designed to do that.  So does the lock statement.  Using lock is by far the best way to stay out of trouble.  I should mention the Interlocked class, but only in passing.

    Hans Passant.
    • Marked as answer by Mark Ritter Friday, August 22, 2008 8:47 AM
    Thursday, August 21, 2008 12:11 AM
    Moderator

All replies

  • From yoyur description it sounds like you would be fine to not lock "X."   The only caveat I can think of is if it is critical for "R" to know about the change by "W" immediately.

    Michael Fischer
    Wednesday, August 20, 2008 3:11 PM
  • Thanks for the answer.
    If I would want that "R" knows instantly about the change in "X" I would have to use another programming scheme anyway. The only additional delay I can think of are the times where the "X" is transferred into a cpu cache or the "R" threads idles after the read of "X" and in the meantime "W" makes the change. In which case there are "different" states of "X" present.
    Anyhow, this is correct by the next read of "X" by "R".
    But I was not sure if there arise any problems with this or I forgot some details in this process.

    Best regards
    Mark Ritter
    Wednesday, August 20, 2008 4:24 PM
  • There are two issues at stake.  First, is the variable that gets updated atomic.  In other words, is there any danger that the other thread will read a partially updated value.  On Intel/AMD architectures, 32-bit variable updates are atomic.  But 64-bit values, like the "long" C# type are not.  Another thread can potentially read the new value of the lower 32 bits but the old value of the upper 32 bits, generating a nonsense value.  A lock will be required for them.

    The second issue is far more troubling and will be an issue when the machine has a CPU with multiple cores.  Like any machine sold in the past several years.  Each CPU core has its own cache, a "buffer" if you will between the core and system memory.  Updates to variables made by one thread (one core) are not visible to another thread (another core) until that core's cache gets refreshed.  Which means that your second thread will quite likely *not* read the value written by your first thread.  It will read a stale value instead.

    Fixing this problem requires implicitly writing code to force the cache to be refreshed.  Thread.MemoryBarrier() was designed to do that.  So does the lock statement.  Using lock is by far the best way to stay out of trouble.  I should mention the Interlocked class, but only in passing.

    Hans Passant.
    • Marked as answer by Mark Ritter Friday, August 22, 2008 8:47 AM
    Thursday, August 21, 2008 12:11 AM
    Moderator
  • You're right to be wary of using lock.  Locking a block in this way means that you can never have more than one thread in this section of the code.  If you have multiple threads you are forcing them to run sequentially whenver they access this variable which may cause a major performance bottleneck.

    In the scenario you describe I would suggest two possible solutions.  As I understand it you have a variable that is false, at some point is set to true, and never gets reset after that.  You can use double-checked locking in this case.  If the variable is true, your just flow through your code as normal, there is no need for locking in this scenario.  If it's currently false, you then enter a lock block and check the value again.  Using this technique you only pay the perfomance penalty of locking until the once-only change occurs.  This technique is often used for the Instance property on a Singleton class.

    Another possibility is to use ReaderWriterLock supplied by the framework.  As the name suggest you mark your accesses to the variable with read locks and any change to it with a write lock.  Any number of readers or a single writer can be in the block at any one time.

    See the MSDN documentation for a code sample of reader-writer locking.

    _
    D
    Thursday, August 21, 2008 8:12 AM
  •  Thanks for your excellent feedback.
    nobugz said:

    ... First, is the variable that gets updated atomic.  ...

    I was not aware that even longs show this problems. I wrote this in bold red letters in my "never forget" list :). I am using a bool variable so this should not be a problem.

    nobugz said:

    ...It will read a stale value instead. ...

    That was the issue i was afraid of. I thought it will get read a bit "later" but it WILL get read. Your scenario suggest that it is possible that the updated value will never get changed.

    DavidBending said:

    ...You can use double-checked locking in this case...
    ...ReaderWriterLock...

    Thanks for your suggestions. The double checked locking won't be necessary because I terminate after the updated value is read.
    Furthermore I transfer said variable (which is a simple value type) per reference to a method where the reading is done. If I would want to lock I would have had to transfer the lock-object (syncRoot), too.
    The MemoryBarrier looked like a good candidate but I think it flushes the whole cache, whereas I need only this single variable to be reread outside the cache. As I use VB.NET I think that Thread.VolatileRead is the right candidate for my scenario.
    Strangely this is implemented for all basic data types BUT NOT for bool. Is there a specific reason behind this ?

    Thanks for your answers
    Mark Ritter
    Thursday, August 21, 2008 3:45 PM