Multithreading is the capacity of a platform (Operating System, Virtual Machine, etc.) or application to generate a process that consists of many threads of execution (threads). The smallest group of instructions that can be separately controlled by a scheduler is referred to as a thread of execution. These threads can execute in parallel, which improves program efficiency.
Multithreading refers to the execution of several threads on multiple cores or processors at the same time in multicore and multiprocessor systems.
The time is divided across the threads in single-core computers via multithreading. A predetermined number of instructions are sent to the CPU by the operating system from each thread. It is not possible to run many threads at once. The operating system just simulates their concurrent execution. This operating system capability is known as multithreading.
In C++11, built-in multithreading capability was made available. Multithreaded C++ program development is made achievable via the header file thread.h
.
A multithreaded application is one that runs multiple threads simultaneously within the application itself.
For example, if we want to design a server that can handle as many concurrent connections as the server can handle, we can achieve this fairly quickly if we create a new thread for each connection. In some instances, a multithreaded server starts a new thread for every incoming connection instead of making a separate socket to handle them, and each new thread also makes a new socket.
The need for multithreading in GUI applications is another common example. GUI applications execute one operation at a time in a single thread (the Main Thread). This thread is either processing an event or waiting for one. As a result, the user interface will freeze while a time-consuming activity is being initiated by the user. Multithreading needs to be used in this situation.
Through the creation of objects belonging to a Thread subclass, the main thread can initiate new threads. When an application is multithreaded, the GUI operates in its own thread while additional processing occurs in other threads. This ensures that the application's GUIs remain responsive even when processing is busy.
To start, make sure your program has a thread header:
Syntax
#include <thread>
To make a thread, you must first construct an object of the thread class.
// This thread is not a representation of any execution thread.
thread t_empty;
As you can see, we don't give any data to the thread when the default constructor of the thread class is used. This indicates that in this thread, nothing is executed. A thread needs to be started. There are numerous ways to accomplish it.
A function pointer can be passed to a thread's function constructor when you create it. As soon as a thread is created, this function begins working in that thread. Consider the following example:
#include <iostream>
#include <thread>
using namespace std;
void threadFunc()
{
cout << "Welcome all to Multithreading" << endl;
}
int main()
{
//pass a function to the thread
thread funcTest1(threadFunc);
}
You will get a runtime problem even though it compiles without any issues
You can see that the main thread establishes funcTest1 as a new thread with the parameter threadFunc. The main thread doesn't wait for funcTest1 to finish. It keeps working. Even when the main thread has finished running, funcTest1 is still active. It leads to errors. Before the primary thread is terminated, all other threads must also be ended.
Thread joining is accomplished by using the thread class's join() member function:
Syntax
void join();
This method only returns once all threads have been terminated. The main thread will wait until the child thread has completed its execution, in other words.
To end a POSIX thread, use the method listed below.
#include <pthread.h>
pthread_exit (status)
Here, a thread is intentionally terminated using pthread exit. The pthread exit()
routine is often invoked when a thread has finished its task and is no longer needed.
The other threads will continue to run even if main()
exits with pthread exit()
before all of the threads it has created have finished. If not, they will be ended automatically after main() is finished.
Example
Using the pthread create()
function, this straightforward sample code creates 5 threads. Each thread ends by calling pthread_exit()
after printing the message "Hello World!"
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid;
tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main () {
pthread_t threads[NUM_THREADS];
int rc;
int i;
for( i = 0; i < NUM_THREADS; i++ ) {
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Output:
main() : creating thread, 0 main() : creating thread, 1 main() : creating thread, 2 main() : creating thread, 3 main() : creating thread, 4 Hello World! Thread ID, 0 Hello World! Thread ID, 1 Hello World! Thread ID, 2 Hello World! Thread ID, 3 Hello World! Thread ID, 4
The above program should be built with the -lpthread library. $gcc test.cpp -lpthread
Execute your program now, and it will produce the results listed above.
With the use of a structure, this example demonstrates how to pass numerous arguments. A thread callback accepts any data type because it points to a void, as seen in the sample below.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
void *PrintHello(void *threadarg) {
struct thread_data *my_data;
my_data = (struct thread_data *) threadarg;
cout << "Thread ID : " << my_data->thread_id ;
cout << " Message : " << my_data->message << endl;
pthread_exit(NULL);
}
int main () {
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc;
int i;
for( i = 0; i < NUM_THREADS; i++ ) {
cout <<"main() : creating thread, " << i << endl;
td[i].thread_id = i;
td[i].message = "This is message";
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Output:
main() : creating thread, 0 main() : creating thread, 1 main() : creating thread, 2 main() : creating thread, 3 main() : creating thread, 4 Thread ID : 3 Message : This is message Thread ID : 2 Message : This is message Thread ID : 0 Message : This is message Thread ID : 1 Message : This is message Thread ID : 4 Message : This is message
The following two procedures can be used to attach or detach threads:
pthread_join (threadid, status)
pthread_detach (threadid)
The pthread join() method pauses the calling thread until the given 'threadid' thread terminates/ends. It is determined whether a thread is joinable or detached when it is generated by one of its attributes. A thread can only be joined if it was created as a joinable thread. A disconnected thread cannot ever be joined after being formed.
Using the Pthread join method, the following example shows how to wait for thread completions.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
int i;
long tid;
tid = (long)t;
sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
}
int main () {
int rc;
int i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
// Initialize and set thread joinable
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for( i = 0; i < NUM_THREADS; i++ ) {
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], &attr, wait, (void *)i );
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
// free attribute and wait for the other threads
pthread_attr_destroy(&attr);
for( i = 0; i < NUM_THREADS; i++ ) {
rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i ;
cout << " exiting with status :" << status << endl;
}
cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}
Output:
main() : creating thread, 0 main() : creating thread, 1 main() : creating thread, 2 main() : creating thread, 3 main() : creating thread, 4 Sleeping in thread Thread with id : 0 .... exiting Sleeping in thread Thread with id : 1 .... exiting Sleeping in thread Thread with id : 2 .... exiting Sleeping in thread Thread with id : 3 .... exiting Sleeping in thread Thread with id : 4 .... exiting Main: completed thread id :0 exiting with status :0 Main: completed thread id :1 exiting with status :0 Main: completed thread id :2 exiting with status :0 Main: completed thread id :3 exiting with status :0 Main: completed thread id :4 exiting with status :0 Main: program exiting.
A process is a resource allocation and protection unit. Some resources, including as virtual memory, I/O handlers, and signal handlers, are managed by processes. Using an MMU, the process is shielded from other processes. Cons: Inter-process communication (IPC) might be difficult and ineffective.
The computational unit that operates within a process is referred to as a thread. A thread is responsible for managing a variety of resources, including the stack, registers, signal masks, priorities, and thread-specific data. pros: IPC between processes is less effective than IPC between threads. Cons: Threads may conflict with one another.