Click here to Skip to main content
15,879,474 members
Articles / Programming Languages / C#
Article

Thread_Resource affinity

Rate me:
Please Sign up or sign in to vote.
3.08/5 (8 votes)
24 Mar 20065 min read 37.4K   187   32   1
How to simplify the synchronization of acces to your resources by using the notion of affinity between threads and resources.

TitlePractical .NET2 and C#2
AuthorPatrick Smacchia
PublisherParadoxalPress
PublishedJanuary 2006
ISBN0-9766132-2-0
PriceUSD 59.95 (ParadoxalPress price: USD 33.99)
Pages896
Websitewww.PracticalDOT.NET (browse and download the 647 listings and sample chapters)

The following article is excerpted from chapter 5 of the book Practical .NET2 and C#2.

Contents

Introduction to thread-resource affinity

You can greatly simplify the synchronization of acces to your resources by using the notion of affinity between threads and resources. The idea is to always access a resource using the same thread. Hence, you can remove the need to protect the resource from concurrent access since it is never shared. The .NET framework presents several mechanisms to implement this notion of affinity.

The System.ThreadStatic attribute

By default, a static field is shared by all the threads of a process. This behavior forces the developer to synchronize all accesses to such a field. By applying the System.ThreadStaticAttribute attribute on a static field, you can constrain the CLR to create an instance of this static field for each thread of the process. Hence, the use of this mechanism is a good way to implement the notion of affinity between threads and resources.

It is better to avoid directly initializing such a static field during its declaration. In fact, in this case, only the thread which loads the class will complete the initialization of its own version of the field. This behavior is illustrated by the following program:

Example1.cs

C#
using System.Threading;
class Program {
   [System.ThreadStatic]
   static string str = "Initial value ";
   static void DisplayStr() {
      System.Console.WriteLine("Thread#{0} Str={1}",
                               Thread.CurrentThread.ManagedThreadId , str);
   }
   static void ThreadProc() {
      DisplayStr();
      str = "ThreadProc value";
      DisplayStr();
   }
   static void Main() {
      DisplayStr();
      Thread thread = new Thread( ThreadProc );
      thread.Start();
      thread.Join();
      DisplayStr();
   }
}

This program displays the following:

Thread#1 Str=Initial value
Thread#2 Str=
Thread#2 Str=ThreadProc value
Thread#1 Str=Initial value

Introduction to Thread Local Storage (TLS)

The notion of affinity between threads and resources can also be implemented with the concept of thread local storage (often called TLS). This concept is not new, and exists at the level of Win32. In fact, the .NET framework internally uses this implementation.

The concept of TLS uses the notion of a data slot. A data slot is an instance of the System.LocalDataStoreSlot class. A data slot can also be seen as an array of objects. The size of this array is always equal to the number of threads in the current process. Hence, each thread has its own slot in the data slot. This slot is invisible to other threads. It can be used to reference an object. For each data slot, the CLR takes care of establishing a correspondence between the threads and their objects. The Thread class provides the two following methods to allow read and write access to an object stored in a data slot:

C#
static public object GetData( LocalDataStoreSlot slot );
static public void SetData( LocalDataStoreSlot slot, object obj );

TLS named data slots

You have the possibility of naming a data slot in order to identify it. The Thread class provides the following methods in order to create, obtain, or destroy a named data slot:

C#
static public LocalDataStoreSlot AllocateNamedDataSlot( string slotName );
static public LocalDataStoreSlot GetNamedDataSlot( string slotName );
static public void FreeNamedDataSlot( string slotName );

The garbage collector does not destroy named data slots. This is the responsibility of the developer.

The following program uses a named data slot in order to provide a counter to each thread of the process. This counter is incremented for each call to the fServer() method. This program takes advantage of the TLS in the sense where the fServer() method does not take a reference to the counter as a parameter. Another advantage is that the developer does not need to maintain a counter himself for each thread.

Example2.cs

C#
using System;
using System.Threading;
class Program {
   static readonly int NTHREAD = 3;    // 3 threads to create.
   // 2 calls to fServer() for each thread created.
   static readonly int MAXCALL = 2;
   static readonly int PERIOD = 1000; // 1 second between calls.
   static bool fServer() {
      LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot( "Counter" );
      int counter = (int) Thread.GetData( dSlot );
      counter++;
      Thread.SetData( dSlot, counter );
      return !( counter == MAXCALL );
   }
   static void ThreadProc() {
      LocalDataStoreSlot dSlot = Thread.GetNamedDataSlot( "Counter" );
      Thread.SetData( dSlot, (int) 0 );
      do{
         Thread.Sleep( PERIOD );
         Console.WriteLine(
            "Thread#{0} I’ve called fServer(), Counter = {1}",
            Thread.CurrentThread.ManagedThreadId ,
            (int)Thread.GetData(dSlot));
      } while ( fServer() );
      Console.WriteLine("Thread#{0} bye",
         Thread.CurrentThread.ManagedThreadId );
   }
   static void Main() {
      Console.WriteLine( "Thread#{0} I’m the main thread, hello world",
                         Thread.CurrentThread.ManagedThreadId );
      Thread.AllocateNamedDataSlot( "Counter" );
      Thread thread;
      for ( int i = 0; i < NTHREAD; i++ ) {
         thread = new Thread( ThreadProc );
         thread.Start();
      }
      // We don’t implement a mechanism to wait for child threads
      // completion, thus, we are waiting for a while. 
      Thread.Sleep( PERIOD * (MAXCALL + 1) );
      Thread.FreeNamedDataSlot( "Counter" );
      Console.WriteLine( "Thread#{0} I’m the main thread, bye.",
                         Thread.CurrentThread.ManagedThreadId );
   }
}

This program displays the following:

Thread#1 I’m the main thread, hello world
Thread#3 I’ve called fServer(), Counter = 0
Thread#4 I’ve called fServer(), Counter = 0
Thread#5 I’ve called fServer(), Counter = 0
Thread#3 I’ve called fServer(), Counter = 1
Thread#3 bye
Thread#4 I’ve called fServer(), Counter = 1
Thread#4 bye
Thread#5 I’ve called fServer(), Counter = 1
Thread#5 bye
Thread#1 I’m the main thread, bye.

TLS anonymous data slots

You can call the AllocateDataSlot() static method of the Thread class to create an anonymous data slot. You are not responsible for the destruction of an anonymous data slot. However, you must make it so that an instance of the LocalDataStoreSlot class be visible by all the threads. Let’s rewrite the previous program using the notion of an anonymous data slot:

Example3.cs

C#
using System;
using System.Threading;
class Program {
   static readonly int NTHREAD = 3;    // 3 threads to create.
   // 2 calls to fServer() for each thread created.
   static readonly int MAXCALL = 2;
   static readonly int PERIOD = 1000; // 1 second between calls.
   static LocalDataStoreSlot dSlot;
   static bool fServer() {
      int counter = (int) Thread.GetData( dSlot );
      counter++;
      Thread.SetData( dSlot, counter );
      return !( counter == MAXCALL );
   }
   static void ThreadProc() {
      Thread.SetData( dSlot, (int) 0 );
      do{
         Thread.Sleep(PERIOD);
         Console.WriteLine(
            "Thread#{0} I’ve called fServer(), Counter = {1}",
            Thread.CurrentThread.ManagedThreadId ,
            (int)Thread.GetData(dSlot));
      } while ( fServer() );
      Console.WriteLine( "Thread#{0} bye",
         Thread.CurrentThread.ManagedThreadId );
   }
   static void Main() {
      Console.WriteLine( "Thread#{0} I’m the main thread, hello world",
                         Thread.CurrentThread.ManagedThreadId );
      dSlot = Thread.AllocateDataSlot();
      for ( int i = 0; i < NTHREAD; i++ ) {
         Thread thread = new Thread( ThreadProc );
         thread.Start();
      }
      Thread.Sleep( PERIOD * (MAXCALL + 1) );
      Console.WriteLine( "Thread#{0} I’m the main thread, bye.",
         Thread.CurrentThread.ManagedThreadId );
   }
}

The System.ComponentModel.ISynchronizeInvoke interface

The System.ComponentModel.ISynchronizeInvoke interface is defined as follows:

C#
public object System.ComponentModel.ISynchronizeInvoke {
    public object       Invoke( Delegate method, object[] args );
    public IAsyncResult BeginInvoke( Delegate method, object[] args );
    public object       EndInvoke( IAsyncResult result );
    public bool         InvokeRequired{ get; }
}

An implementation of this interface can make sure that certain methods are always executed by the same thread, in a synchronous or asynchronous way:

  • In the synchronous scenario, a thread T1 calls a method M() on an object OBJ. In fact, T1 calls the ISynchronizeInvoke.Invoke() method by specifying a delegate object which references OBJ.M() and an array containing its arguments. Another thread T2 executes the OBJ.M() method. T1 waits for the end of the call, and retrieves the information after the return of the call.
  • The asynchronous scenario is different than the synchronous scenario by the fact that T1 calls the ISynchronizeInvoke.BeginInvoke() method. T1 does not remain blocked while T2 executes the OBJ.M() method. When T1 needs the information from the return of the call, it will call the ISynchronizeInvoke.EndInvoke() method which will provide this information if T2 completed the execution of OBJ.M().

The ISynchronizeInvoke interface is mainly used by the framework to force the Windows Forms technology to execute the methods of a form using the thread dedicated to the form. This constraint comes from the fact that the Windows Forms technology is built on top of the Windows messages plumbing. The same kind of problem is also addressed by the System.ComponentModel.BackgroundWorker class.

You can develop your own implementations of the ISynchronizeInvoke interface by inspiring yourself from the Implementing ISynchronizeInvoke example provided by Juval Lowy.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
Patrick Smacchia is a .NET MVP involved in software development for over 15 years. He is the author of Practical .NET2 and C#2, a .NET book conceived from real world experience with 647 compilable code listings. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange at Société Générale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET application.

Comments and Discussions

 
GeneralThank you Pin
Avi Farah17-May-09 17:44
Avi Farah17-May-09 17:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.