MULTITHREADING IN C

Introduction:

In C, the term "multithreading" describes the use of numerous threads concurrently. Each thread does a different task. Due to the concurrent nature of multithreading, many tasks may be carried out at once. Additionally, multithreading reduces the CPU's resource usage. There are two categories of multitasking: process-based and thread-based. When anything is described as multithreading, it signifies that at least two or perhaps more threads are running in the same process at once. We must first understand what a thread and a process are in order to comprehend multithreading in C. Let's look at these subjects to gain a better understanding.

What are processes and threads?

A thread is the fundamental building block of any process's execution. A program is made up of several processes, and each process is made up of threads, which are much more basic units. Therefore, the thread can be considered the fundamental building block of a process or the simpler unit that jointly determines CPU utilization.

The following items are included in a thread:

Thread ID:

It is a special thread ID that is generated at the time of thread formation and retained for that specific thread's duration.

Program counter:

It is a value that the hardware loads.

A registered set:

It is a collection of common registers.

A stack:

It is a remnant of that specific thread.

In addition, if two threads work simultaneously in the same process, they share code, data sections, and other operating system resources like file opens and signals. A heavyweight process, a type of conventional process, can control one thread. However, a multi-thread of control has the capacity to open and carry out multiple tasks concurrently. The system becomes considerably more effective as a result of using threads, which is why they are useful.

The distinction between single and multithreading in C is explained. First of all, it is a single-threaded process. As a result, the entire block-including the code, data, etc.-is regarded as one process, and that process only has one thread. It signifies that this technique will complete just one task at a time. But there is a multithreading process that stands in opposition to this. There are activities like code, stack, data, and files as well, but they are being performed by several threads, each of which has its own stack and registers. Given that numerous tasks can be completed at once in this situation, the process is known as a multithreading process.

Thread comes in two varieties:

Thread at user level:

It is at the user level, as the name would imply. The kernel is not given access to its data.

Thread at the kernel level

The kind of thread refers to the thread's relationship to the kernel and the operating system of the system.

Process- The series of steps taken to carry out a program can be referred to as the process. A program is not immediately executed when it is run. It is broken down into a few basic steps that are carried out sequentially in an organized manner to lead to the execution of a process eventually.

A process that has been broken down into smaller steps is referred to as a "clone or child process", while the original process is referred to as the "parent" process. In the memory, each process uses a certain amount of space that is not shared with any other processes.

A procedure goes through some stages prior to execution.

NEW-

In this situation, a new process is generated.

READY-

When a process is prepared and waiting for a processor to be assigned, it is in this state.

RUNNING-

When the process is active, it is the state.

WAITING-

When a process is in this state, something is waiting to happen.

TERMINATED-

It is the state in which the procedure is being carried out.

Why is C multithreaded?

Multithreading in the C idea can be leveraged through parallelism to enhance an application's functionality. Consider the case where you have several tabs open in a browser window. Then, each tab operates concurrently and might be referred to as a Thread. Assuming we use Microsoft Excel, one thread will manage text formatting, and one thread will handle input. Therefore, C's multithreading feature makes it simple to carry out multiple tasks at once. The creation of a thread is considerably quicker. The context transfer across threads happens more quickly. Additionally, communication between threads can be made more quickly, and thread termination is simple.

How to Write C Programmes for Multithreading?

Although multithreading applications are not built-in to the C language, it is possible depending on the operating system. The threads.h standard library is used to implement the multithreading idea in C. However, there is currently no compiler that can do this. We must employ platform-specific implementations, such as the "POSIX" threads library, by using the header file pthread.h, if we want to use multithreading in C. "Pthreads" is another name for this. A POSIX thread can be created in the following ways:

In this case, Pthread_create creates a new thread in order to make the thread executable. It allows you to implement multithreading in C as many times as you like in your code. The parameters and their descriptions from earlier are listed here.

thread:

It is a singular identification that the subprocess returns.

attr:

When we want to set thread attributes, we use this opaque attribute.

start_routine:

When start_routine is generated, the thread will run a routine.

arg:

The parameter that the start_routine receives. NULL will be used if no arguments are given.

Certain C multithreading examples

Here are some examples of multithreading issues in C.

1. The Reader-Writer Issue

A common operating system issue with process synchronization is the reader/writer problem. Assume we have a database that Readers and Writers, two different user categories, can access. Readers are the only ones who can read the database, whereas Writers are the only ones who can read the database and update it as well. Let's use IRCTC as a simple example. If we wish to check the status of a specific train number, simply enter the train number into the system to view the pertinent train information. Only the info that is present on the website is shown here. The read operator is this. However, if we want to reserve a ticket, we must fill out the ticket booking form with details like our name, age, and so on. So, we'll perform a write operation here. There will be some adjustments made to the IRCTC database.

The issue is that several people are simultaneously attempting to access the IRCTC database. They might be a writer or a reader. The issue arises if a reader is already utilizing the database and a writer accesses it simultaneously to work on the same data. Another issue arises when a writer uses a database, and a reader accesses the same information as the database. Thirdly, there is a difficulty when one writer updates the database while another is attempting to update data on the same database. The fourth scenario occurs when two readers attempt to retrieve the same material. All these issues arise if the reader and writer use the same database data.

Semaphore is a method that is employed to solve this issue. Let's look at an illustration of how to use this issue.

Reader process:

Output:

Reader 1 reads data: 0
Reader 2 reads data: 0
Reader 3 reads data: 0
Reader 4 reads data: 0
Reader 5 reads data: 0

Explanation:

In this code, we have the shared variable data and the reader count rc. The wrt condition variable is used to limit access for the writer process, and the mutex is used to guarantee mutual exclusion for accessing the shared data.

The reader process is represented by the reader() function. The reader count (rc) is increased before attaining the mutex lock. It uses pthread_cond_wait() to wait on the wrt condition variable if it is the first reader (rc == 1). As a result, writers will be prevented from writing until all readers have completed.

The reader process checks if it was the last reader (rc == 0) and lowers the reader count (rc--) after reading the shared data. If it was, pthread_cond_signal() signals the wrt condition variable to let waiting writer processes continue.

Using the pthread_create() and pthread_join() functions, we new and join multiple reader threads in the main() function. An individual ID is assigned to each reader thread for identifying purposes.

Writer process:

In the above example, same as the reader process, an operation known as the wait operation is carried out on "wrt" when a user wishes to access the data or object. After that, the new user won't be able to access the object. And once the user has finished writing, another signal operation is performed on wrt.

2. lock and unlock problem:

The idea of a mutex is utilized in multithreading in C to guarantee that there won't be a race condition between the threads. When multiple threads begin processing the same data at once, this circumstance is known as racing. However, if these circumstances exist, we must. We use the mutex's lock() and unlock() functions to secure a particular section of code for a specific thread. Such that, another thread cannot begin performing the same operation. The "critical section/region" is the name given to this protected code area. Before using the shared resources, we set up a lot in a certain area, and once we've finished using them, we unlock them once more.

Let's examine the operation of the mutex for locking and unlocking in multithreading in C:

Example:

Output:

Thread 1: Shared data modified. New value: 1
Thread 2: Shared data modified. New value: 2
Thread 3: Shared data modified. New value: 3
Thread 4: Shared data modified. New value: 4
Thread 5: Shared data modified. New value: 5

Explanation:

In this above example, we explain how we lock and unlock a certain region of code that shields us from the racing situation. 'pthread_mutex_t' is used as an initializer in the example above. 'pthread_mutex_lock' is then written before the beginning of the code that we want to lock. The coding that we wish to lock is finished after that. After that, the locking of the code is terminated using 'pthread_mutex_unlock'; going forward, no code will be in lock mode.

The Dining Philosopher Problem:

One of the classic issues with synchronization is the dining philosopher issue. Simple resource allocation for several processes is required but shouldn't result in a stalemate or hunger. The dining philosopher problem can be viewed as a straightforward representation of a number of processes, each of which is demanding resources. Since each of these processes requires a resource allocation, it is necessary to distribute those resources across all of the processes so that no one process ever gets stuck or stops working.

Assume there are five philosophers seated at a circle-shaped table. They eat at one point and ponder about something at another. Around the round table, the philosophers are evenly spaced out on the chairs. Additionally, there is a bowl of rice and five chopsticks for each philosopher in the middle of the table. When the philosopher feels she cannot interact with her colleagues who are seated nearby.

A philosopher occasionally takes up two chopsticks when she becomes hungry. She chooses two chopsticks from her neighbors-one on her left and one on her right-that are within easy reach. But the philosopher should never pick up more than one chopstick at once. She will obviously be unable to pick up the chopstick that the neighbor is using.

Example:

Let's use an example to demonstrate how this is implemented in C.

Output:

Philosopher 0 is thinking.
Philosopher 1 is thinking.
Philosopher 2 is thinking.
Philosopher 3 is thinking.
Philosopher 4 is thinking.

Philosopher 0 is eating.
Philosopher 1 is eating.
Philosopher 2 is eating.
Philosopher 3 is eating.
Philosopher 4 is eating.

Philosopher 0 Finished eating
Philosopher 1 Finished eating
Philosopher 2 Finished eating
Philosopher 3 Finished eating
Philosopher 4 Finished eating

Explanation:

Chopsticks can be represented by a semaphore. Since there are chopsticks on the table and no philosopher has chosen one, all of the chopsticks' components are first initialized to 1. Now that chopstick[i] has been chosen as the first chopstick. chopstick[i] and chopstick[(i+1)%5] are subject to the first wait operation. These chopsticks' wait operation indicates that the philosopher has picked them up. The eating process begins once the philosopher selects his chopstick. The signal operation is now carried out on the chopsticks [i] and [(i+1)%5] once the philosopher has finished eating. The philosopher then turns back to sleep.

To determine whether the subthread has joined the main thread or not, we used the pthread_join function. Similarly, we have checked whether the mutex lock has been initialized using the pthread_mutex_init method.

To initialize and verify whether the new thread was created or not, we utilized the pthread_create function. Similar to this, we destroyed the mutex lock using the pthread_mutex_destroy function.

The Producer-Consumer Problem:

A common issue with multithreading process synchronization is the producer-consumer problem. Two processes are present in it: the first is the producer's process, and the second is the consumer's process. Furthermore, it is assumed that both operations are occurring concurrently in parallel. Additionally, they are a cooperative process, which implies that they are sharing something with one another. It is important that when the buffer is full, the producer cannot add data. When the buffer is empty, the consumer cannot extract data from the buffer because the common buffer size between the producer and the consumer is fixed. The issue is stated in this way. Hence, to implement the Producer-Consumer problem and solve it, we shall employ the idea of parallel programming.

Example:

Output:

1. producer
2. consumer
3. for exit
Please enter your choice:

Explanation:

We perform two tasks. The functions consumer() and producer() indicate the status and operation of the consumer and producer. The producer() method will create the mutex lock and determine whether the buffer is full when it is called. When the buffer is full, nothing will be produced. If not, it will create, and then, after the production, it will put itself to sleep to unlock the mutex lock. Like the producer, the consumer first creates the mutex lock, checks the buffer, consumes the product, and then releases the lock before going back to sleep.

A counter (x) will be used during manufacturing and will keep growing until the manufacturer produces the item. However, the consumer will make fewer of the same manufactured item (x).

Conclusion:

The idea of using two or more threads to execute a program is known as multithreading in the C programming language. Multithreading allows for the simultaneous execution of several tasks. The simplest executable component of a program is a thread. The process is the idea that a task can be completed by breaking it up into several smaller sub-processes.

The header file pthread.h is required in order to implement multithreading in C since it cannot be done directly.






Latest Courses