What is Multithreading in C?
Multithreading in C refers to the use of many threads inside a single process. Each thread serves a separate function. Multithreading operates concurrently which means numerous jobs may be executed simultaneously. Multithreading also minimizes the consumption of resources of the CPU. Multitasking can be divided into two types: process based and thread based. By saying Multithreading, it means that there are at least two or more than two threads in the process that runs simultaneously. For understanding multithreading in c, first, we need to learn about what is thread and what is a process. Lets us see these topics for better understanding.
What are Thread and Processes?
- Threads: A thread is a basic unit of execution of any process. A program comprises many processes and all the processes comprise much simpler units known as threads. So, the thread can be referred to as the basic unit of a process or it is the simpler unit that tother makes the CPU utilization. To know more about threads in multithreading, you can visit Threads in Operating System.
A thread comprises the following thing:-
- A thread ID: It is a unique ID of the thread that is created during the creation of the thread and it remains for the whole life of that particular thread.
- Program Counter: It is a value that loads into the hardware.
- A registered set: It is a set of general registers.
- A stack: It is the memory of that particular thread.
Apart from these, if two threads are working inside the same process, they share their code, their data section, and also share the other resources operating system like file open and signals. A traditional process that is also called a heavyweight process can control a single thread. But, if we talk about a multi-thread of control, it is capable of opening and executing more than one task at the same time. That is why threads become useful and how using threads makes the system much more efficient. Let us see how multithreading in c affects a process by using the diagram given below.
Here, the difference between single and multithreading in c is depicted. In the first diagram, it is a single-threaded process. So, the whole block content shown in the diagram like code, data, etc is considered a single process and the process contains only one thread. It means this process will perform only one task at a time. But in contrast to this, the second diagram is a multithreading process. There are also tasks represented by blocks like code, stack, data, files, etc but it is having multi-threads over there and each of these threads has there own stack and registers. In this case, this process can perform multiple tasks at a time and is hence called the Multithreading process.
There are two types of Thread:
- User-level thread: As the name suggests, it is at the user level. Its information is not shared with the kernel.
- Kernel level thread: It is the type of thread that is operated by the operating system of the system and the kernel from which the thread belongs.
Process The process can be defined as the series of actions that are done to execute a program. When a program is run, it is not directly gets executed. It gets divided into some steps of small actions and these actions get executed one by one in a systematic way which ultimately leads to the execution of a process. When a process is divided into simpler actions, it is known as the " clone or child process" and the main process is the "parent" process. All the processes consume some space in the memory and these spaces are not interchanged with other processes.
There are some stages of a process that it gets before execution.
- NEW- this is the state where a new process is created.
- READY- this is the state when a process is ready and waiting for the allocation of the processor.
- RUNNING- this is the state when the process is running.
- WAITING- this is the state when the process is waiting for some event.
- TERMINATED- this is the state when the process gets executed.
Why Multithreading in C?
Multithreading in the C concept can be used to improve the working of an application through parallelism . Suppose, if we are using a browser and we are working on many tabs in a browser window. Then, each of the tabs works parallelly and can be referenced as Thread . Suppose, we are using MS Excel, then one thread controls text formatting, and one thread processes input. So, multithreading in C makes it easy to perform more than tasks simultaneously. Thread is much faster to create. The switching of context between the threads is faster. Also, making communication between the threads is faster and the termination of threads is also easy.
How to Write Multithreading Programs in C?
In C language, there is not any built-in support for multithreading applications but it can do multithreading depending upon the operating system. The standard library used for implementing the concept of multithreading in C is known as <threads.h> but it is not possible to implement it using any known compiler yet. If we want to use multithreading in C then we must use some platform-specific implementations like the "POSIX" threads library by using the header file pthread.h . This is also known as "pthreads". We can create a POSIX thread in the following ways:
Here, for making the thread executable, 'pthread_create makes a new thread. In this way, the multithreading in C can be done as many times as you want in your code. Following are the parameters and their description that we used above.
- thread: it is a unique identifier that is returned by the subprocess.
- attr: it is an opaque attribute that is used when we want to set thread attributes.
- start_routine: the thread will execute a routine when start_routine is created.
- arg: The argument that passes to the start_routine. If no argument is passed then, NULL will be used.
Examples for Multithreading in C:
Following are examples of problems related to multithreading in C.
1. Reader Writer Problem
Reader writer problem is a standard problem of process synchronization in the operating system. Suppose we have a database that can be accessed by two types of users i.e.- Reader and Writer. Readers are those who only read the database and Writers are those who can read the database and also they can update the database as well. Let us take a simple example of IRCTC if we want to check the train status of a particular train number, then we give the train number as input and see the related information of the train. Here, we only see the data that is available on the website. This is the read operator. But if we want to book a ticket then we will put our pieces of information like name, age, etc in the ticket booking form, So, here we will do write operation. The database of IRCTC will face some changes into it.
The problem is that multiple users are accessing the database of IRCTC at the same time. They may be a reader or maybe a writer. If one reader is already using the database then at the same time a writer comes to the databases on the same data, then this is the problem. When a writer is using the database and then a reader comes to the same data as the database, this is also a problem. The third case is when a writer is updating the database and at the same time another writer tries to update data on the same database then this is also a problem. The fourth case is when two reader tries to fetch the same data. All these problems will occur only if the reader and writer are on the same data of the database.
For resolving this problem, a technique called semaphore is used. Let's see an example to implement this problem.
- Reader Process:
In the above example, mutex and art are the semaphores, their initialization is at 1. The wait is used to decrease the value by 1. The number of readers that are accessing the data is termed as 'rc'. When 'rc' becomes 1, the 'wrt' uses the wait operation. It means the writer will now be unable to access the objects. In this way, the read operation is completed. When the 'rc' becomes 0, wrt uses the signal operation. After that, the objects will be accessible to the writer.
- Writer Process:
In the above example, when a user wants to access the data or object, an operation called the wait operation is done on 'wrt'. After that, access to the object for the new user will be prohibited. And when the user completes the writing operation, again signal operation is done on wrt.
2. Lock and Unlock Problem
Mutex is the concept that is used in Multithreading in c that ensures that there should not be a condition of race among the threads. Racing is the condition of threads when more than one thread starts processing the same data at the same time. Although if these conditions occur, then we need to. We use lock() and unlock() on mutex to protect a specific region of code for a particular thread. So that no other thread can start executing the same task. This protected region of code is known as the "critical section/region". We set a lot on a particular region before using the shared resources and when are done with the use of that shared resource then we again unlock it.
Let us see how mutex works for locking and unlocking in Multithreading in c:
Explanation
3. The Dining Philosopher Problem:
The dining philosopher problem is one of the classical problems in the synchronization problem. It is a simple need to allocate several resources for several processes and it should not be in a deadlock and starvation manner. We can consider the dining philosopher problem as the simple representation of several processes and each process is requesting resources we need to allocate the resources for all the different processes such that deadlock never occurs and none of the processes stops anytime. Let us see a diagram to visualize the dining philosopher problem.
Suppose five philosophers are sitting at a circular table. At one time, they eat and at another time they think about something. The philosophers are sitting on the chairs around the round table, equidistant from each other. And there is a bowl of rice in the center of the table with five chopsticks for each philosopher for eating. The time when the philosopher thinks she is unable to communicate with other colleagues sitting around her.
From time to time, a philosopher picks up two chopsticks when she gets hungry. She picks two chopsticks that she can easily pick that is one from her left neighbor and the other from the right neighbor. But there should be only a single chopstick picked up at a single time, by the philosopher. Obviously, she will not be able to pick up the chopstick that is occupied by the neighbor. Let us see the implementation of this in C language using an example.
Explanation: We can use a semaphore to represent the chopsticks. Since the chopsticks are present on the table and no philosopher picked any of the chopsticks so, initially all the elements of chopsticks are initialized to 1. Now, the first chopstick is picked which is chopstick[i] and the first wait operation is performed on chopstick[i] and chopstick[(i+1)%5]. The wait operation on these chopsticks denotes that the ith philosopher has picked up the chopsticks. After the philosopher has picked his chopstick, the eating operation is performed. Now, when the philosopher has stopped eating, the signal operation is performed on the chopstick[i] and chopstick[(i+1)%5]. And finally, the philosopher goes back to sleep.
Note:
- We have used the pthread_join function to check whether the subthread has joined the main thread or not. Similarly, we have used the pthread_mutex_init function to check whether the mutex lock has been initialized or not.
- We have used the pthread_create function to initialize and check whether the new thread is created or not. Similarly, we have used the pthread_mutex_destroy function to destroy the mutex lock.
4. The Producer-Consumer Problem
The producer consumer problem is a standard problem of multithreading process synchronization. In it we have two processes, one is the producer's process and the second is the consumer's process. And the assumption is that both processes are coming parallel at the same time. And also they are a cooperative process which means they are sharing something between themselves. Since the common buffer size between the producer and the consumer is fixed, it is necessary that when the buffer is full producer can not add data and when the buffer is empty, the consumer can not extract data from the buffer. This is the problem statement. So., we will use the concept of parallel programming to implement the Producer-Consumer problem and get rid of this problem.
Let us see code for the consumer and producer.
Output:
Explanation: We have two functions consumer() and the producer() function that denotes the state and working of the consumer and producer When we call the producer() function, it will create the mutex lock and check whether the buffer is full or not. If the buffer is full, it will not produce anything. Else, it will produce and after the production, it will release the mutex lock by making itself sleep. Similarly, the consumer will first create the mutex lock, check the buffer, consume the produced item, and finally releases the mutex lock and then goes back to sleep.
During the production, a counter (x) will be used and it will keep on increasing until the producer produces the item. On the other hand, the consumer will decrease the count of the same produced item (x).
Learn about C Programming Tutorial:
You can learn more about C programming in depth at C programming Tutorial-
Conclusion:
- Multithreading in C implies the concept of using two or more two threads for executing a program.
- By using Multithreading, more than two actions can be performed parallelly.
- Thread is the most basic unit of a program that executes.
- Process is the concept of executing a task by dividing the process into many small sub-processes.
- Multithreading in C can not be implemented directly, instead, we need to use the header file pthread.h.