Concurrent Systems and Multithreading
Concurrent Systems and Multithreading
Concurrent system: A system where multiple tasks appear to run at the same time (can be
truly parallel or interleaved on a single CPU).
Multithreading: A way to achieve concurrency by running multiple threads (lightweight units
of a process) simultaneously.
Example:
Imagine a video player: one thread handles video, one handles audio, and another listens for
keyboard input.
Concurrency is the ability of a system to handle multiple tasks at once (or appear to).
It could be:
A multithreaded program,
1. Threads :
Small independent units of execution inside a program.
🧵 In C++, threads are created using the std::thread class (from the <thread> header).
Eg :
std::thread t1(task1);
2. Functions to Execute :
Each thread needs a function or callable to run.
Eg.
void task1() {
std::cout << "Running task 1\n";
}
3. Thread Creation :
Launch the thread by passing the function to std::thread.
Eg. std::thread t1(task1); // t1 runs task1
4. Synchronization :
Managing shared resources to prevent conflicts between threads.
🛑 For this, we use things like:
std::mutex (mutual exclusion)
std::lock_guard (auto-lock)
std::unique_lock
Eg.
std::mutex m;
void task() {
std::lock_guard<std::mutex> lock(m);
// Safe access here
}
5. Joining Threads :
Wait for threads to finish before the program ends.
Eg.
t1.join(); // Main thread waits for t1
void task1() {
std::cout << "Task 1 running\n";
}
void task2() {
std::cout << "Task 2 running\n";
}
int main() {
std::thread t1(task1); // runs concurrently
std::thread t2(task2); // runs concurrently
t1.join();
t2.join();
}
Possible Output:
Task 1 running
Task 2 running
OR
Task 2 running
Task 1 running
pthread stands for POSIX Threads (Portable Operating System Interface (uniX)).
It is a C-style thread library used in UNIX/Linux systems.
It is lower-level and gives you manual control over threads.
Sample code:
#include <iostream>
#include <thread>
using namespace std;
void task() {
cout << "Thread running using std::thread\n";
}
int main() {
thread t1(task); // Start thread
t1.join(); // Wait for it to finish
return 0;
}
In C++ you can pass a class (or more precisely, a callable object) to a thread
instead of a function.
What is operator()?
Sample code:
#include <iostream>
#include <thread>
using namespace std;
int main() {
Task taskObj;
thread t1(taskObj); // Pass class object to thread
t1.join();
return 0;
}
Output:
Thread running using class (functor)
Description: A thread object is created, but the thread itself hasn't started running yet.
Syntax:
Description: After a thread is created, it becomes runnable. This means the thread is ready and
waiting for the operating system to assign it CPU time to actually run the function.
Syntax:
3. Running
Description: The thread starts running the function you assigned to it. This happens when the
thread gets CPU time.
Syntax (in the function you want the thread to run):
void myFunction() {
std::cout << "Thread is running!" << std::endl;
}
Description: Sometimes a thread might wait or be blocked (e.g., waiting for data, waiting to
acquire a lock, etc.). This can happen if you use sleep, or if you are synchronizing threads using a
mutex.
Syntax (blocking with sleep/mutex):
1. std::this_thread::sleep_for(std::chrono::seconds(2)); // simulates waiting/blocking
2. std::mutex mtx; // Mutex for synchronization
void threadFunction(int id) {
std::cout << "Thread " << id << ": Trying to lock the mutex...\n";
mtx.lock(); // Thread tries to lock the mutex, blocking if it's already locked by another
thread
std::cout << "Thread " << id << ": Got the lock!\n";
std::cout << "Thread " << id << ": Finished work, unlocking the mutex.\n";
mtx.unlock(); // Unlock the mutex so others can acquire it
}
5. Terminated (Finished)
Description: Once the thread finishes its work, it is terminated. The thread has completed its
execution and can be joined with the main thread.
Syntax:
What is Race Condition : A race condition occurs when two or more threads try to access and modify
shared data at the same time in an unpredictable way. Because of this, the final outcome of the
program may be different each time it runs, depending on the order in which the threads run.
In simpler terms:
When threads "race" to access shared data, and the result depends on who gets there first. 🏁
🧠 Key Concept:
Sample code :
#include <iostream>
#include <thread>
using namespace std;
int main() {
thread t1(increment); // Thread 1
thread t2(increment); // Thread 2
cout << "Final counter value: " << counter << endl;
return 0;
}
Concurrent Systems and Multithreading
Output :
May / May not be 200000
Each thread reads the value of counter, increments it, and writes it back. If two threads do this
at the same time, they may overwrite each other’s changes, leading to errors.
Since the threads are racing, the final value of counter may not be 200000 (which it should
be, because each thread increments it by 100,000).
It could be much smaller due to lost updates!
Sample code :
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main() {
Concurrent Systems and Multithreading
thread t1(increment); // Thread 1
thread t2(increment); // Thread 2
cout << "Final counter value: " << counter << endl;
return 0;
}
Output :
200000
✅ Mutex:
Using mutex ensures that only one thread can access the shared counter at a time.
This prevents race conditions and gives you the correct result!