0% found this document useful (0 votes)
4 views37 pages

CS0051 - Module 02

The document covers concurrent programming in C++, focusing on thread management, data sharing, and synchronization mechanisms such as mutexes and atomic operations. It discusses the importance of handling race conditions and introduces concepts like the producer-consumer problem, as well as practical code examples. Additionally, it highlights the use of std::lock_guard for managing mutexes effectively to prevent deadlocks and ensure safe access to shared data.

Uploaded by

Chloe C.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views37 pages

CS0051 - Module 02

The document covers concurrent programming in C++, focusing on thread management, data sharing, and synchronization mechanisms such as mutexes and atomic operations. It discusses the importance of handling race conditions and introduces concepts like the producer-consumer problem, as well as practical code examples. Additionally, it highlights the use of std::lock_guard for managing mutexes effectively to prevent deadlocks and ensure safe access to shared data.

Uploaded by

Chloe C.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

Module 2

Concurrent Programming
Module 1
Subtopic 1
Concurrency in C++
• To install appropriate IDE and build tools for C++
• To perform various ways of specifying code to run on a
new thread
• To perform simple thread management
• To implement data sharing among threads
• To determine problems associated with sharing data
between threads
https://en.cppreference.com/w/cpp/thread
#include <iostream>
#include <thread>

using namespace std;

int main() {

cout << "Hello World from main()" << endl;

cout << this_thread::get_id() << endl;

}
int main()
#include <iostream>
{
#include <thread>
cout << "Hello World from main()" << endl;
using namespace std; thread t1(hellofunction);

HelloObject ho;
void hellofunction(){ thread t2(&HelloObject::objectfunction, &ho);
cout << "Hello from function..." << endl; //runs HelloObject::objectfunction() on object ho
}
t1.join();
class HelloObject {
public:
t2.join();
void objectfunction();
};
}
void HelloObject::objectfunction(){
cout << "Hello from object function" <<endl;
}
• When a thread object goes out of scope and it is in
joinable state, the program is terminated.
• join() - waits for the thread to finish its execution

• detach() - permits the thread to execute independently


from the thread handle
int main()
{
cout << "Hello World from main()" << endl;
thread t1(hellofunction);

HelloObject ho;
thread t2(&HelloObject::objectfunction, &ho); //runs HelloObject::objectfunction() on object
ho

t1.join()
t2.join()

cout << "\nmain() program ends..." << endl;

}
int main()
{
cout << "Hello World from main()" << endl;
thread t1(hellofunction);

HelloObject ho;
thread t2(&HelloObject::objectfunction, &ho); //runs HelloObject::objectfunction() on object ho

t1.detach();
t2.detach();

cout << "\nmain() program ends..." << endl;

}
#include <iostream>
#include <thread>
int main()
{
using namespace std; HelloObject ho;
thread t1(ho);
t1.join();
class HelloObject {
public: cout << "\nmain() program ends..." << endl;
void operator()(){
}
cout << "Hello object method thread..." << endl;

}
};
#include <iostream> int main()
#include <thread> {
#include <string>
cout << "Hello World from main()" << endl;
using namespace std; thread t1(hellofunction,5, "FEUTECH");

HelloObject ho;
void hellofunction(int n, string str){ thread t2(&HelloObject::objectfunction, &ho, 5, "FEUTECH");
t1.join();
for(int i=0; i<n; i++)
t2.join();
cout << "Hello from function... " << " " << str << endl;
}
cout << "\nmain() program ends..." << endl;
class HelloObject {
public:
void objectfunction(int n, string str); }
};

void HelloObject::objectfunction(int n, string str){

for(int i=0; i<n; i++)


cout << "Hello from object function" << " " << str << endl;
}
https://en.cppreference.com/w/cpp/thread
void hellofunction(int n, string str){

for(int i=0; i<n; i++){


cout << "Hello from function... " << " " << str << " " << this_thread::get_id << endl;
this_thread::sleep_for(2000ms);
}
}
#include <iostream>
#include <thread>
#include <string>
#include <chrono>

class HelloObject {
public:
void objectfunction(int n, string str);
};

void HelloObject::objectfunction(int n, string str){


this_thread::sleep_until(chrono::system_clock::now() + 5000ms );
for(int i=0; i<n; i++)
cout << "Hello from object function" << " " << str << endl;
}
One of the key benefits of using threads for concurrency
is the potential to easily and directly share data between
them.

However there are some issues surrounding shared data


#include<chrono>

auto start = chrono::high_resolution_clock::now();


// do something
auto end = chrono::high_resolution_clock::now();

auto duration = chrono::duration_cast<chrono::microseconds>(end-start);


cout << "Time Taken : " << duration.count() << " microseconds" << endl;

std::chrono::duration - cppreference.com
A race condition is an undesirable situation that occurs when a device or
system attempts to perform two or more operations at the same time, but
because of the nature of the device or system, the operations must be done
in the proper sequence to be done correctly.
• In computing, the producer-consumer problem is a family of
problems described by Edsger W. Dijkstra since 1965.
Code Demo of Producer-Consumer Problem
(Race Condition)
• Mutex stands for mutual exclusion. It ensures that only one thread can
access a critical section at any one time.

• In C++, a mutex (short for mutual exclusion) is a synchronization


primitive used to protect shared data from being accessed
simultaneously by multiple threads. This helps prevent data races and
ensures data consistency.
Here are some key points about std::mutex:

Locking and Unlocking:

• lock(): Blocks the calling thread until it successfully locks the


mutex.
• unlock(): Releases the mutex, allowing other threads to
acquire it.
• try_lock(): Attempts to lock the mutex without blocking.
Returns true if successful, false otherwise
#include <iostream> int main() {
#include <thread> std::thread t1(print_thread_id, 1);
#include <mutex> std::thread t2(print_thread_id, 2);

std::mutex mtx; t1.join();


t2.join();
void print_thread_id(int id) {
mtx.lock(); return 0;
std::cout << "Thread #" << id << std::endl; }
mtx.unlock();
}
Code Demo of Producer-Consumer Problem
(Using a Mutex)
std::atomic is a C++ feature that ensures safe access to shared data in
multi-threaded programs without using locks.

Key Points:
• Atomic Operations: These are operations that complete without
interruption, preventing race conditions.
• Thread Safety: std::atomic makes sure that multiple threads can safely
read and write shared data.
• Efficiency: Many atomic operations are lock-free, meaning they don't use
mutexes and are faster.
#include <atomic> int main() {
#include <iostream> std::thread t1(increment);
#include <thread> std::thread t2(increment);

std::atomic<int> counter(0); t1.join();


t2.join();
void increment() {
for (int i = 0; i < 1000; ++i) { std::cout << "Counter: " << counter << std::endl;
++counter; return 0;
} }
}
Code Demo of Producer-Consumer Problem
(Using Atomics)
Feature std::atomic std::mutex
Purpose Safe, single operations on data Safe, multiple operations on data
Speed Faster, no locks Slower, uses locks
Complexity Simple More complex
Deadlock Risk None Possible
Use Case Counters, flags Complex data structures
While std::atomic helps prevent race conditions by ensuring atomic
operations, race conditions can still occur if multiple atomic operations need
to be performed together. This is because std::atomic only guarantees
atomicity for individual operations, not for sequences of operations.
#include <atomic>
int main() {
#include <iostream>
std::thread t1(set_a_then_b);
#include <thread>
std::thread t2(check_b_then_a);
std::atomic<int> a(0);
t1.join();
std::atomic<int> b(0);
t2.join();
void set_a_then_b() {
return 0;
a.store(1);
}
b.store(1);
}

void check_b_then_a() {
if (b.load() == 1 && a.load() == 0) {
std::cout << "Race condition detected!" << std::endl;
}
}
Using std::lock_guard in C++ is an elegant way to manage mutexes and ensure that they
are properly locked and unlocked, preventing potential deadlocks and race conditions.
Here's a simple guide to using std::lock_guard:

#include <iostream>
int main() {
#include <thread> thread t1(print_message, "Hello from thread 1");
#include <mutex> thread t2(print_message, "Hello from thread 2");

using namespace std; t1.join();


t2.join();
mutex mtx;

void print_message(const std::string& message) {


return 0;
lock_guard<std::mutex> guard(mtx); // Locks the mutex }
cout << message << endl;
// The mutex will be automatically released when the guard goes out of scope
}
The std::lock_guard<std::mutex> locks the mtx mutex when the guard object is
created.

When the guard object goes out of scope (e.g., the function exits), the mutex is
automatically unlocked.

This ensures that the critical section (accessing std::cout in this case) is safely
managed.
Code Demo of Producer-Consumer Problem
(Using lock guards)
• Rainer Grimm, Concurrency with Modern C++, March (LeanPub) 2019.
• https://en.cppreference.com/w/cpp

You might also like