BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Joe Duffy on Concurrency

Joe Duffy on Concurrency

With dual and quad-core CPUs finding their way onto personal computers and 32-core processors predicted in the next 3 to 5 years, concurrency is becoming a major concern for developers. But without good library support, programming for concurrency is hard. Joe Duffy, author of Professional .NET Framework 2.0 and the upcoming Concurrent Programming on Windows presents his opinions and recommendations for creating reusable, concurrent libraries in .NET.

When building frameworks, code that potentially calls other code should be avoided while holding locks. This includes virtual methods, which are just as unpredictable as any over user code when overridden. Joe explains…

This applies to most virtual, interface, and delegate calls while a lock is held—as well as ordinary statically dispatched calls—into subsystems with which you aren’t familiar.  The more you know about the code being run when you hold a lock, the better off you will be.  If you follow this approach, you’ll encounter far fewer deadlocks, hard-to-reproduce reentrancy bugs, and surprising dynamic composition problems, all of which can lead to hangs when your API is used on the UI thread, reliability problems, and frustration for your customer.  Locks simply don’t compose very well; ignoring this and attempting to compose them in this way is fraught with peril.

Speaking of locks, there is a class of objects that should never be locked. The effects of locking these objects, informally known as AppDomain-agile objects, are not well known to most developers.

Instances of some Type objects are shared across AppDomains.  The most notable are Type objects for domain neutral assemblies (like mscorlib) and cross-assembly interned strings.  While it may look innocuous, locks taken on these things are visible across all AppDomains in the process.  As an example, two AppDomains executing this same code will stop all over each other:

lock (typeof(System.String)) { … }

This can cause severe reliability problems should a lock get orphaned in an add-in or hosted scenario, possibly causing deadlocks from deep within your library that seemingly inexplicably span multiple AppDomains.  The resulting code also exhibits false conflicts between code running in multiple domains and therefore can impact scalability in a way that is difficult for customers (and library authors!) to reason about.

Taking thread-switching into consideration is also important, especially when dealing with P/Invoke calls.

Always access the “last error” after an interop call via Marshal.GetLastWin32Error.

If you mark a P/Invoke signature with [DllImportAttribute(…, SetLastError=true)], then the CLR will store the Win32 last error on the logical CLR thread.  This ensures that, even if a fiber switch happens before you can check this value, your last error will be preserved across the physical OS threads.  The Win32 APIs kernel32!GetLastError and kernel32!SetLastError, on the other hand, store this information in the TEB.  Therefore, if you are P/Invoking to get at the last error information, you are apt to be quite surprised if you are running in an environment that permits thread migration.  You can avoid this by always using the safe Marshal.GetLastWin32Error function.

 

Rate this Article

Adoption
Style

BT