MultiThreading — Look at Thread

Omid Eidivandi
5 min readOct 3, 2021

--

In the previous article we talked about the Threading process and concerning resources , in this article we explore some details about threads and how they consume the resources.

Thread

What’s a thread and what does it do? simply , a thread is an executing context in a process, and it is managed by a library.

There are two level of threads : Kernel Level and User Level (KLT , ULT)

KLT : The Kernel takes care of the threads and processes in the system based on it’s ThreadTable side of traditional ProcessTable. but consider the existence of ThreadTable in each process. The KLT has the advantage of centralized management and time slicing and surely they offer less state allocation and initialization.

ULT : This level is beneficial when the goal is obtaining a cheaper overhead ,complexity and much faster treatment. They are all managed by runtime library as System.Threading in .Net and the kenrel will not have any knowledge about so they are treated as single-thread processes.

System Configuration :

Before we begin look at diagnostics of our code , let’s have a look at OS configuration and capacities, Below there is the screenshot of the local system in which i m running this code.

Add alt text

This machine has one Dual Core processor with a base speed of 1,51 GHz and 4 Logical processors that means there are for threads concurrent execution at the same time.

.Net Threads:

We create a thread easily using system.threading in .net

Thread t = new Thread(MethodToRun);

And Run It by

t.Start();

Now , let’s see what happens beside the scene , using VS2017 diagnostics tool we can retrieve all information about CPU and Memory as below

Add alt text

As it’s clear in the image we have 2 threads which are the main program thread and our thread created in code with a total of 192 (2 * 96)bytes as memory on the heap in an x64 processor architecture for a x86 it will be as 104(2 * 52)

There are two snapshots of memory allocation , the first is before join and second is after thread termination and returning back to the principle thread.

Let’s have a look on processing unit , essentially there is no CPU consuming code here , but we take a look .

Our code snippet will look like as bellow:

static void Main()
{
Thread t = new Thread(Sleepit);
t.Start();

Console.ReadKey();
}
public void Sleepit()
{
Thread.Sleep(30000);
}

CPU :

The program consumes 79 samples of 1000 sample/second but if you had a look on the list it is evident having 76 samples for the Main program initialization , a temporary 4 samples is not at all a huge degree of CPU usage.

Add alt text

Memory :

The Memory usage is variable based on the code steps, at the beginning of the program main we achieve 86 instances and on the start step this raises to 95, this number will be decreased to 93 after Thread.Sleep(30000) and by running the Console.ReadKey() we achieve 277.

Add alt text

As you see in a very simple and small program most of Memory consummation was the Program own memory usage, but what are these ? The CultureData, NumberFormats and etc .

Add alt text

Cost of Threads :

The cost of threads is context switching and context initialization , what’s gonna happen when a thread achieve it’s time slice end and the Scheduler try to do the switch for each thread with a priority of execution a ExecutionContext verification will be done, the Thread class has a static CurrentThread property in which the thread with the priority will be sets before any other operation, the swich process verify this with current thread and if they are equals it verifies the executioncontext if it’s already initialized or not , and return the corresponding context .

Execution Context :

The ExecutionContext is not just a placeholder or container to keep the context , it has some other responsibilities as Switch , it has also a security context to verify as well as the synchronization and flow execution context , The Locals will be verified here and this class has the responsibility of reading and writing them.

StackSize:

If you know about the size of stack you desire for the thread just easily pass it through constructor as bellow

Thread tr = new Thread(myMethod, myStackSize);

Nothing worse if you don’t pass it, just by default it will verify the Prossecor Default StackSize.

Running ExecutionContext:

The ThreadHelper will be in charge of running the ExecutionContext by passing the generated executing context , a callback context and the threadhelper as state object.

Some Consideration :

Setting the stacksize is a good idea if you know what happens in your code , for example you need to have less stack size but add more threads on the server , so set it to 256 KB instead of 1 MB and in some situations you need the large stack but less threads , But there are some specific details to know before doing so , your stacksize can not go over than the default stack size (1 MB) in a non fully trusted code, and the min can not be less than 256 KB .But let the default stacksize be used for ~100% of the times.

--

--

Omid Eidivandi
Omid Eidivandi

Written by Omid Eidivandi

i'm a technial lead , solution/softwatre architect with more than 20 years of experience in IT industry,, i m a fan of cloud and serverless in practice

No responses yet