Threading deep dive – Day 9

Atomic operation on Multi-core / Multi processors machine.

Executing atomic operation is relatively less expensive In a single processor / core machine, when compared to the multi processor machine. Operating system on single processor machine can simply make sure the scheduler does not preempt the atomic operation executing thread by raising the IRQL is sufficient. In fact some of the atomic operations [like XADD] are supported at assembly level.

But in case of multi-core / processor machine, there may be chance that multiple processor are trying to modify the same memory location. This case has to be handled differently. We all know the processor access the values from the caches. The caches are capable of holding multiple values. When one processor modifies one specific cache-line, the corresponding cache-lines accessed by other processors would be invalidated. This is achieved with the help of bus transaction. During the bus transaction all the processors are stopped and corresponding cache-lines are invalidated. Then the modified value would be transferred to the main memory. So when other processors need the value , it would try to access it from the cache. Since the cache-line is invalidated, the value would be fetched from memory. The processor that writes the cache-line owns the owner key and that key would be moved across the processors whoever writes the same cache line. The more number of owner keys need to be transferred across processors involves more number bus transactions. Here I briefed the common technique. But this implementation vary from one architecture to other. There are around 8 to 9 protocols on how to share values across processors caches. More number of Bus transactions reduces application performance. For more details refer this link - https://cache-www.intel.com/cd/00/00/01/76/17689_w_spinlock.pdf

Managed Thread and Native Thread

Operating systems do not understand what is managed thread. Os know only native threads. So when you create a managed thread, you are simply creating a managed object. That is not a thread. This object holds fields like delegate method, exception state and name etc. But these information is not sufficient to create a native thread which is actually a real thread . CLR internally maintains another object that contains all the fields required to create a native thread. So the managed thread object refers to a native object created by CLR and this native object refers to the actual thread. So when a developer creates the managed thread the below happens.

a. Managed Thread object created.

b. CLR internally creates a native object that holds all the info enough to create a thread.

 When the developer calls a start method on the managed thread object below steps happen.

a. The internal native object creates a new / re-uses a native thread

b. The native thread start executing the appropriate delegate.

 So the native thread is always referred through native object internally created by the CLR. The advantage here is the native object can decide whether to reuse the old native thread or to create a new native thread. When the thread execution is over the native thread is not gets deleted immediately. The CLR keep the native thread for sometime even after the execution is over. Within the stipulated time if another request for a thread comes, the native object reuses the already created native thread.

 Go through the below code snippet , execute and see the result to understand how native threads are re used in CLR.

static int endCount = 0;
static int NonDuplicateCount = 0;
static void Main(string[] args)
{
Console.WriteLine("Start");
Console.ReadLine();
for(int i = 1; i < 4000; i++)
{
Thread t = new Thread(WaitCallback);
t.Start();
}
Console.ReadLine();
}

public static void WaitCallback(object state)
{
if (ht.ContainsKey(AppDomain.GetCurrentThreadId()))
{
Console.WriteLine("Same native thread");
Console.WriteLine("The Native ThreadID {0} and old managed Thread ID {1}", AppDomain.GetCurrentThreadId(), ht[AppDomain.GetCurrentThreadId()]);
Console.WriteLine("The Native ThreadID {0} and new managed Thread ID {1}", AppDomain.GetCurrentThreadId(),
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(++endCount);
Console.WriteLine("NonDuplicateCount {0}", NonDuplicateCount);
Console.ReadLine();
}
else
{
ht.Add(AppDomain.GetCurrentThreadId(), Thread.CurrentThread.ManagedThreadId);
}
}

CLR team might have thought of using fibers in place of native thread. I hope that’s why they have provided the one indirection between managed thread and the native thread. As far my knowledge goes, fibers are used only in SQLServer. What are fibers? Let us see about them next day.