Multithreading in C#: Processes, Threads, and Performance Optimization

We already know about concurrency and have talked about Multithreading in general.

This topic is going to cover multithreading in more detail. You’re going to learn.

  1. The process is the definition and PID
  2. The reality of running multiple Thread

The process is the definition and PID

From the operating system's perspective, a process is a fundamental unit of execution that encompasses a running application. It includes.

  • Code: The instructions that make up the application's functionality.
  • Data: The information the application needs to operate, including variables and memory allocations.
  • Resources: The access to system resources like CPU time, memory, and files that the process needs to function.

When a process is created, the operating system assigns a unique numerical identifier called a Process Identifier (PID). This PID serves as a handle for the operating system to manage and track the process.

Operating system

Key Roles of PID

  • Tracking: The PID enables the operating system to keep track of the process's status, resource usage, and interactions with other processes.
  • Management: It allows the operating system to perform actions on the process, such as starting, stopping, pausing, or resuming it.
  • Communication: It facilitates communication between processes, as they can reference each other using their PIDs.

While a single-threaded application is typically mapped to a single process, multi-threaded applications can involve multiple threads within a single process. Threads share the same process address space and resources but can execute independently.

To make sure, just run the command line(Win+R -> cmd ) and type “tasklist”.

(Win+R -> cmd )

By default, every process consists of a single thread, called the Main thread. It is a UI thread for UI applications. So, what Thread is?

Thread is a single, sequential flow of control. It is a special “path” that your code instructions flow.

 Sequential flow of control

The reality of running multiple Threads

But how about multithreading? From the main thread, it is possible to “raise” another thread, called “worker threads”. Their responsibility is to “do more work” without overloading the Main one.

Multithreading

We use the Main /UI thread only for lightweight operations. Consider Web API application. Your UI thread should always be “alive” to respond to requests from the User side. If you have long-running operations, you must forward them to secondary/worker threads.

Let's start from the old scenario. A long time ago, there were CPUs only with one core, and handling multithreading was a little bit complicated. One Thread usually means it is possible at the same time to run only one Thread. But in this case, how to run multithread threads at the same time? Well, physically it is not possible but by using a special technique you can gain an effect of “multithreading”. Let me explain it.

The operating system will rapidly switch between the threads, giving the illusion of simultaneous execution. The OS manages which thread runs at any given moment, deciding when to context switch between them. You can't predict the exact sequence in which the threads will run, although they will both make progress. If the threads access shared data, you must use synchronization mechanisms like locks or mutexes to prevent race conditions and ensure data consistency.

Operating systems use scheduling algorithms to distribute CPU time across active threads in multithreaded situations. Round-robin scheduling is a popular method in which each thread is given a tiny time slice (quantum) to run before it is suspended and another thread takes over. Many times, this quantum is called the scheduling quantum or the time quantum. It does not, however, directly correlate with "quantum time" and does not always specify how long each thread runs.

Active threads

Individual threads do not execute simultaneously on a single-core CPU. The operating system creates the appearance of concurrent execution by quickly switching between them. There is some granularity to this switching, and it may be affected by thread priority, OS implementation, and device architecture.

Fortunately, we have multiple cores nowadays but we can run more threads than the count of the cores.

When you have a CPU with 4 cores and 10 threads, your system can handle multithreading through a combination of hardware capabilities and software management

Hardware

Cores: Your CPU itself has 4 physical cores, these are separate processing units that can run instructions concurrently. In other, 4 threads can run parallel and each one jumps on separately from its core.

Hyperthreading (HT)/Simultaneous Multithreading (SMT): HT is supported by the majority of modern CPUs like Intel or SMT in AMD. This way, each physical core can process two threads ‘concurrently.’ Although — strictly speaking— not synchronous execution since both the processing entitles advance thanks to sharing of resources and utilize ability inside such cored. Moreover, in your instance with 4 cores HT/SMT, the CPU can support up to 8 threads at once on dedicated cores or “light” manage an additional two through its HT and SMT.

Software

Operating System (OS): No role can be focused as even more critical than the one played by OS in dictating how threads are ‘attached’ to cores and when those get until they start execution. Since the number of threads in a system is higher than its cores, such an OS shapes advanced algorithms like round robin to give off some CPU time among them.

Context Switching: Thread-switching is a process whereby the state of the running thread and that of the next one has to be saved or loaded. Some overhead is introduced to the process and, if there are too many threads involved in such a system design approach this may adversely affect performance.

So, your 4 cores and 10 threads enable not only four true simultaneity but also up to potential additional ‘concurrent’ executions through HT/SMT. But these 10 threads mainly share the processing power that is present in this quad-core processor, and they are likely to be prone to context-switching overhead.

Effectiveness of multithreading

Effectiveness of multithreading depends on the type of tasks you're running.

The first step of solving the problem will be identifying all items to which carbon may leak, and then finding ways whereby avoid leaks.

CPU-bound tasks: This is because these tasks heavily rely on the CPU and can be fairly improved using parallelization over multiple cores or threads. Large numbers of threads with little cores imply that initially it will increase the performance, but as switching cost increases diminishing returns set in.

I/O-bound tasks: These processes spend long periods waiting for data from storage or any other external resources (I/O) hence, the use of a CPU is not fully efficient. In this regard, increasing threads will yield only minor performance improvement.

Mixed workloads: In fact, in most real-world workloads there is a combination of CPU-bound and I/O tasks. The ideal number of threads for a specific workload depends on the element that is most important in subsequent parallelization.

Conclusion

This does not necessarily mean more threads implies better performance. It is contingent on the exact hardware, software, and workload that you have.

The bottleneck of the overhead related to context switching is formed whenever there are many threads.

The optimal number of threads varies depending on the particular case. Experiment and observe the performance to identify an optimum configuration.


Recommended Free Ebook
Similar Articles