views:

178

answers:

4

Hi, so far I thought that any operation done on "shared" object (common for multiple threads) must be protected with "synchronize", no matter what. Apparently, I was wrong - in the code I'm studying recently there are plenty of classes (thread-safe ones, as the Author claims) and only one of them uses Critical Section for almost every method.

How do I find what parts / methods of my code needs to be protected with CriticalSection (or any other method) and which not?

So far I haven't stumbled upon any interesting explanation / article / blog note, all google results are:

a) examples of synchronization between thread and the GUI. From simple progressbar to most complex, but still the lesson is obvious: each time you access / modify the property of GUI component, do that in "Synchronize". But nothing more.

b) articles explaining Critical Sections, Mutexes etc. Just a different approaches of protection/synchronization.

c) Examples of very very simple thread-safe classes (thread safe stack or list) - they all do the same - implement lock / unlock methods which do enter/leave critical section and return the actual stack/list pointer on locking.

Now I'm looking for explanation which parts of code should be protected.

could be in form of code ;) but please don't provide me with one more "using Synchronize to update progressbar" ... ;)

thank you!

+5  A: 

You are asking for specific answers to a very general question.

Basically, apart of UI operations, you should protect every shared memory/resource access to avoid two potentially competing threads to:

  • read inconsistent memory
  • write memory at the same time
  • try to use the same resource at the same time from more than one thread... until the resource is thread-safe.

Generally, I consider any other operation thread safe, including operations that access not shared memory or not shared objects.

For example, consider this object:

type
  TThrdExample = class
  private
    FValue: Integer;
  public
    procedure Inc;
    procedure Dec;
    function Value: Integer;
    procedure ThreadInc;
    procedure ThreadDec;
    function ThreadValue: Integer;
  end;

ThreadVar
  ThreadValue: Integer;

Inc, Dec and Value are methods which operate over FValue field. The methods are not thread safe until you protect them with some synchronization mechanism. It can be a MultipleReaderExclusiveWriterSinchronizer for Value function and CriticalSection for Inc and Dec methods.

ThreadInc and ThreadDec methods operate over ThreadValue variable, which is defined as ThreadVar, so I consider it ThreadSafe because the memory they access is not shared between threads... each call from different thread will access different memory address.

If you know that, by design, a class should be used only in one thread or inside other synchronization mechanisms, you're free to consider that thread safe by design.

If you want more specific answers, I suggest you try with a more specific question.

Best regards.

EDIT: Maybe someone say the integer fields is a bad example because you can consider integer operations atomic on Intel/Windows thus is not needed to protect it... but I hope you get the idea.

jachguate
I don't want more specific answer. In fact, I'm even looking for more general ones. I need to learn a lot about threading, and I'm looking for a good "entry point" ;) Especially since I'm using Delphi since version 6 and NEVER ever seen ThreadVar statement...
migajek
BTW shouldn't the third point be "try NOT to use ..." ? ;)
migajek
@migajek: No, the third point is correct. jachguate is saying what two threads might be doing that would require synchronization.
Michael Madsen
@Michael thanks. Reading the whole point again helped ;P It's because 2 am I guess...
migajek
@migajek: not sure about which version introduced ThreadVar... maybe Delphi 5, just can't remember.
jachguate
@jachguate: Integer fields are only atomic if they are aligned on a DWord boundary, wich they "usually" are but don't have to be so your example stands. When threading is involved, it's better to err on the safe side.
Lieven
@Lieven: you're right about err on the safe side... I'm of a _defensive programmer_ kind
jachguate
+2  A: 

You misunderstood TThread.Synchronize method.

TThread.Synchronize and TThread.Queue methods executes protected code in the context of main (GUI) thread. That is why you should use Syncronize or Queue to update GUI controls (like progressbar) - normally only main thread should access GUI controls.

Critical Sections are different - the protected code is executed in the context of the thread that acquired critical section, and no other thread is permitted to acquire the critical section until the former thread releases it.

Serg
+1  A: 

You use critical section in case there's a need for a certain set of objects to be updated atomically. This means, they must at all times be either already updated completely or not yet updated at all. They must never be accessible in a transitional state.

For example, with a simple integer reading/writing this is not the case. The operation of reading integer as well as the operation of writing it are atomic already: you cannot read integer in the middle of processor writing it, half-updated. It's either old value or new value, always.

But if you want to increment the integer atomically, you have not one, but three operations you have to do at once: read the old value into processor's cache, increment it, and write it back to memory. Each operation is atomic, but the three of them together are not.

One thread might read the old value (say, 200), increment it by 5 in cache, and at the same time another thread might read the value too (still 200). Then the first thread writes back 205, while the second thread increments its cached value of 200 to 203 and writes back 203, overwriting 205. The result of two increments (+5 and +3) should be 208, but it's 203 due to non-atomicity of operations.

So, you use critical sections when:

  1. A variable, set of variables, or any resource is used from several threads and needs to be updated atomically.
  2. It's not atomic by itself (for example, calling a function which is guarded by critical section inside of the function body, is an atomic operation already)
himself
+1  A: 

Have a read of this documentation

http://www.eonclash.com/Tutorials/Multithreading/MartinHarvey1.1/ToC.html