Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Concurrency, Part 3 - What if you can't avoid the issue?

Concurrency, Part 3 - What if you can't avoid the issue?

  • Comments 12
Yesterday, I talked about how you deal with concurrency issues by simply avoiding the concurrency issues - essentially by ensuring that your data is only ever accessed by a single thread.

But, of course, there are times when this is unavoidable.  For example, if your code is located in a DLL, there's no way of knowing how your caller's going to be calling your code.

If you're a COM component, then you can resolve the issue by marking your component apartment threaded and then checking to see if the thread on which you're being called is the thread on which you were first instantiated (the IWinHttpRequest object does this, for example).  Problem solved :)  Of course, this pushes the problem off to your caller - they're stuck with dealing with the issues of marshalling your calls to the right thread, etc. 

Of course this isn't scalable, and it's really unfriendly, so we need a more friendly solution (and one that allows for a more scalable solution).  Since concurrent programming is all about protecting your data from being accessed by multiple threads, then really, all you need to do is to ensure that only one thread in your process can access your shared (global) data.

Windows provides a relatively simple mechanism for serializing code execution, EnterCriticalSection and LeaveCriticalSection (yeah, I know you already know that :)).

Again, if you're interested in protecting your data, simply initialize a critical section in your startup, call EnterCriticalSection on every entry, and LeaveCriticalSection on exit.  Problem solved, you won't have to worry about concurrency issues.  Again, you're not going to be scalable, but at least you're not forcing your clients to work overtime.  An example of a component that does this is Exchange's MAPI client DLL (or at least it used to do this when I worked on it, it might have changed).

But this still isn't scalable.  So the next step is to identify the fields you want to protect and implement a critical section around them.  For example, by default, each Win32 heap has a critical section associated with it.  When you call HeapAlloc(), the heap logic enters the critical section, performs the allocation and leaves the critical section.  Similarly, HeapFree() enters the critical section, performs the free, and leaves the critical section.

For a huge number of scenarios, that's sufficient - you simply identify the data that's going to be protected, wrap it in a critical section, enter the critical section before you access the data, leave the critical section when you're done, and you're good to go.

But there's a caveat to wrapping your data structures with critical sections.  It often doesn't work if you have more than one type of data structure being protected.  In fact, if your code is reasonably sophisticated, you've got a potential problem..

And that's Larry's second principle of concurrency: "Critical sections can be your best friend (unless they're your worst enemy)".    I'll talk about why this is tomorrow.

 

Edit: Added a second principle (I knew I forgot something :))

 

  • Here's my guess:

    The scenario: the DLL has two functions, one to initiate a request, the second to get the results. Even if each function is thread-safe, i.e. EnterCriticalSection upon entry and LeaveCriticalSection before returning, the caller has to be careful that two threads don't both issue requests, then get mixed up as to which response they get. I've done this in network programming where each thread just ends up with garbage as it reads from the middle of another thread's packet.

    Another example is iterators over a data structure. They're valid until another thread changes the data structure.

    How'd I do?
  • Actually, I didn't know it was a quiz. But you're absolutely right, this is an issue. I'll make sure I talk about it a bit in the future.

  • But Raymond gives us quizzes... ;) Keep the posts coming, Larry, it's good stuff.
  • The quiz was where you said there's a caveat and never mentioned what it was. Maybe apartment threading is a reference to it, but I'm not sure -- I know nothing about COM.

    I love reading your blog. Your combination of extensive experience, intelligence, and humility are the hallmarks of a great teacher.
  • Ah, didn't figure that out :) Actually the caveat was a teaser for tomorrows post :) I'm trying to chop this up into 500ish word pieces so that I don't get swamped and I couldn't fit a deadlock discussion into that length of post.
  • > I couldn't fit a deadlock discussion into
    > that length of post.

    Sure you could. Just write it in two 250-word chunks, try to add both chunks at the same time, and make sure you don't take your two locks in the same order in both saves.
  • If it's a quiz, this is my guess :)

    Thread 1 :

    EnterCriticalSection(A);
    ...
    EnterCriticalSection(B);

    Thread 2 :
    EnterCriticalSection(B);
    ...
    EnterCriticalSection(A);

    --> deadlock :(
  • Why would you need to serialize code execution? I mean, it's almost always read-only...
  • Josh,
    Because the code's what's accessing your data.

    Data doesn't execute, code manipulates data, and thus needs to be serialized.

  • Hi Larry and thanks for your great blog.

    My question is : are critical section system-wide synchronization primitives, or do they offer only per-process synchronization?

    Here's my case :

    I get a DLL loaded by several processes that has a .shared (linker tip) variable of type CTime. I already know it is a security issue thanks to <a href="http://weblogs.asp.net/oldnewthing" target="_new">Raymond Chen</a>.

    I did two Read/Write accessors functions in my DLL that use a CRITICAL_SECTION. My processes use only these accessors.

    Can process 1 write meanwhile process 2 reads the value ?

    Thank you!
  • josh: This was kinda difficult for me to grasp when I studied it in university. It should be:

    you simply identify the data that's going to be protected, and wrap all _code that handles the data_ with EnterCriticalSection/LeaveCriticalSection pairs (don't forget to Leave on error paths!)

  • Pascal,
    Critical sections are process-wide, not system-wide. For system wide, use a named mutex (don't forget to ACL it appropriately however)
Page 1 of 1 (12 items)