B-Architectural Consideration




















Threading is not child play

Using thread is a big responsibility and risk as well. Poorly designed multithreaded application could bring nightmare. The moment you decided to use thread there are some fundamental architectural question that  you must ask and answer your self. Answer of each question will change the way threading will be implemented in your application. 

Theses topics are quite wast but there are some early pointers for further study. Theses points are arrange in order of a decision making sequence.

Does Threading really required for four application.

There are several sacenerios in which we use thread in our application but that could have been avoided by using right design. before using thread rethink your application design if the same problem can be solved using mechanism like Application.DoEvent , Timers or Timeout then prefer not to use thread at all.

Does your thread will try to access UI element.

  1. Usually we should avid accessing UI element from another threads, by default CLR does`t allow it unless we turned off unsafe access, but we should use safe practice and must check if Control::InvokeRequired is true then we must call Control.Invoke to safely update control values.  Also checkout the MSDN article How to: Make Thread-Safe Calls to Windows Forms Controls 
  2. Alternatively use SynchronizationContextTaskScheduler (a derived type of TaskScheduler) that schedule all the tasks on GUI threads. We mostly use this in windows forms and WPF applications.
  3. Forms.Timers are pretty good in performing small burst of tasks on GUI Threads

Background Vs Foreground Thread

This is an important consideration that really affect overall application design.

  • Foreground threads should be used for long running task or mission critical task that can not be canceled in between application shutdown.
  • Background threads should be used for long running task that are not mission critical task and can be canceled in between during application shutdown.

Dedicated Thread Vs Thread Pool 

Dedicated thread should be used only when application need full control of task running under thread other wise we should always prefer to use Thread Pool because it has less overhead.

Thread Pool Vs Task 

In normal situations we must prefer to use thread pool  over task, Task should be only used when we need thread poll like mechanism + full control of task under execution such as cooperative cancellation and result collection. 

What Asynchronous Programming Pattern (Join mechanism) will be used.

Join or wait for complete design is also major challenge when writing multithreaded application. Each threading requirement has a different join requirement. Some are supported out of the box and some need several design consideration. This topic will be taken in more details in case study section. The fundamentals architectural of wait of complete is listed below.Actual design depends upon type of threading mechanism used.

  1. Pooling(Busy Wait or Spin Wait)  : Also called pooling mechanism in which caller thread wait for child thread to complete after a particular interval. I waste CPU cycle but easy to implement
  2. Wait Until Done (Fork and Join):Main thread start one or more thread and then calls Thread.Join() on all the child threads. Easiest to implement but blocks caller thread, be whould never use this on GUI thread otherwise GUI will be non responsive till all the thread returns.
  3. Callback Notification: Task (ThreadProc) is designed in such a way that it invokes a call back handler to pass the notification caller thread. This is quite efficient mechanism but needs a bit of development overhead. This is quite use full in ThreadPool like implementation.
  4. Task Instance List Pooling: This mechanism is mostly used in conjunction with thread pool implementation. Basic idea is to wrap the task in an instance object with a property IsComplete then instance is pushed to a queue. Then queue is handed over to thread pool. Main thread periodically check for completion status of each task in Task instance queue
  5. Task Result Mechanism:This mechanism can be used with Threading.Task object that have built in support.

How the result of computation will be collected.

There are three fundamentals mechanism of collecting task result, Callback function, Task Instance , and Threading.Task.Result object. Each of them has it`s own limitation and advantage. This will be discussed in a separate page.

What cancellation mechanism will be used.

This is also an very important decision, There are two type of cancellation mechanism available,

  • Cooperative cancellation: Running task is requested via some mechanism to abort what it is doing
  • Preemptive  cancellation: Owner thread takes control of cancellation and do not request running task to stop gracefully , that`s what we called termination. 

What will be Deadlock/Infinite wait detection.

I most of the cases owner thread must implement a mechanism to get of deadlock situation, easiest implementation is to assign a time out after which blocked thread can be terminated but what will be timeout values , is a matter of debate and application specific. 

Is your methods are thread Safe?

Most of the time when a developer creates a thread he usually thought of  synchronization first, but there are several situations where  synchronizations not at all required. If your method is not updating any static variable , only using value type  or string type for which a copy is created before any processing then no thread synchronization is required.

What synchronization model will be used.

Most of the time developer start thinking of synchronization as early design factor but it should be taken in to consideration only all above question has been answered. There are several synchronization patterns available each with different scope and limitations. Below is the summary of synchronization mechanism and constructs  that can be used  in different situations.

  • Kernel Mode Synchronization Construct
    • Event 
    • Semaphore
    • Mutex
  • User Mode Synchronization Construct and patterns
    • Volatile read write: Perform Atomic read or write at a time.
    • Interlocked methods: perform atomic read and write and operations
    • C# Lock keyword
  • Hybrid Construct
    • The ManualResetEventSlim class.
    • SemaphoreSlim class.
    • The Monitor Class and Sync Blocks
    • The ReaderWriterLockSlim Class
    • The OneManyLock Class
    • The CountdownEvent Class
    • The Barrier Class
This is quite wast area of study and I will publish a dedicated page for this but below I will be consolidating when to use what.

What Locking pattern will be used?

Whatever Synchronization construct you use there are certain locking pattern that have been proven over the time are listed below.

Locking Mechanism

Description

Mutex

A mutually exclusive (or mutex) lock acts as a protective barrier around a resource. A mutex is a type of semaphore that grants access to only one thread at a time. If a mutex is in use and another thread tries to acquire it, that thread blocks until the mutex is released by its original holder. If multiple threads compete for the same mutex, only one at a time is allowed access to it.

Recursive lock

A recursive lock is a variant on the mutex lock. A recursive lock allows a single thread to acquire the lock multiple times before releasing it. Other threads remain blocked until the owner of the lock releases the lock the same number of times it acquired it. Recursive locks are used during recursive iterations primarily but may also be used in cases where multiple methods each need to acquire the lock separately.

Read-write lock

A read-write lock is also referred to as a shared-exclusive lock. This type of lock is typically used in larger-scale operations and can significantly improve performance if the protected data structure is read frequently and modified only occasionally. During normal operation, multiple readers can access the data structure simultaneously. When a thread wants to write to the structure, though, it blocks until all readers release the lock, at which point it acquires the lock and can update the structure. While a writing thread is waiting for the lock, new reader threads block until the writing thread is finished. The system supports read-write locks using POSIX threads only. For more information on how to use these locks, see the pthread man page.

Distributed lock

A distributed lock provides mutually exclusive access at the process level. Unlike a true mutex, a distributed lock does not block a process or prevent it from running. It simply reports when the lock is busy and lets the process decide how to proceed.

Spin lock

A spin lock polls its lock condition repeatedly until that condition becomes true. Spin locks are most often used on multiprocessor systems where the expected wait time for a lock is small. In these situations, it is often more efficient to poll than to block the thread, which involves a context switch and the updating of thread data structures. The system does not provide any implementations of spin locks because of their polling nature, but you can easily implement them in specific situations. For information on implementing spin locks in the kernel, see Kernel Programming Guide.

Double-checked lock

A double-checked lock is an attempt to reduce the overhead of taking a lock by testing the locking criteria prior to taking the lock. Because double-checked locks are potentially unsafe, the system does not provide explicit support for them and their use is discouraged.

What Threading Model Application is Using?

TBD

Threading and Synchronization best practices.

  1. My recommendation always is to avoid writing code that blocks any threads in other words try to avoid using any kind of synchronization at all. At the end of day it degrade application performance.
  2. If locking (synchronization)is necessary first try to use the VolatileRead, VolatileWrite, and Interlocked methods because they are fast and they also never block a thread.
  3. Whenever possible always avoid to use dedicated thread  and use solutions like thread pool or task scheduler. 
  4. Use the kernel object constructs only if you want to synchronize threads that are running in different AppDomains or processes.
  5. To atomically manipulate state via a set of operations, use the Monitor class (alternatively lock keyword) with a private field.
  6. Use a reader-writer lock instead of Monitor if multiple threads reads the data but only one modified it, which improves overall performance and minimizes the chance of blocking threads.
  7. Avoid using recursive locks (especially recursive reader-writer locks) because they hurt performance.
  8. If recursive lock is to be used use Monitor that is recursive and its performance is very good because it is a hybrid construct and written in native code.
  9. Avoid releasing a lock in a finally block because entering and leaving exception-handling blocks incurs a performance hit,
  10. Any lock must always be used with timeout and your code should not hold the lock for a long time because this increases the likelihood of threads blocking.


Comments