Multi-Threading
Multi-threading allows you to execute multiple threads concurrently, enabling you to perform tasks simultaneously and take advantage of multi-core processors for increased performance. C++11 and later versions introduced standardized thread support to the language, making it easier to create and manage threads.
Here’s a detailed explanation of multi-threading in Modern C++ along with examples:
Table of Contents
Creating Threads
You can create threads using the std::thread
class from the <thread>
header. It takes a callable object (like a function or a lambda) as an argument that will be executed by the thread.
Example:
#include <iostream>
#include <thread>
// Function to be executed by the thread
void threadFunction() {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread function executing..." << std::endl;
}
}
int main() {
// Create a thread and launch it by calling the function
std::thread t(threadFunction);
// Wait for the thread to finish
t.join();
std::cout << "Main function executing..." << std::endl;
return 0;
}
In this example, we create a new thread t
that will execute the threadFunction
. The join()
function is called on the thread object to wait for the thread to finish before the main thread continues.
Passing Arguments to Threads
You can pass arguments to the thread function by simply providing them as additional arguments when creating the thread.
Example:
#include <iostream>
#include <thread>
void printNumber(int number) {
std::cout << "Number: " << number << std::endl;
}
int main() {
int num = 42;
// Create a thread and pass the argument
std::thread t(printNumber, num);
t.join();
return 0;
}
Using Lambda Functions with Threads
Lambda functions provide a concise way to define small functions without the need for separate function declarations. You can use them with threads to define the thread’s behavior inline.
Example:
#include <iostream>
#include <thread>
int main() {
int x = 10;
int y = 20;
// Create a thread with a lambda function
std::thread t([&]() {
int result = x + y;
std::cout << "Result: " << result << std::endl;
});
t.join();
return 0;
}
Thread Synchronization with Mutex
When multiple threads access shared resources simultaneously, it can lead to data races and undefined behavior. To avoid this, you can use std::mutex
(mutual exclusion) to protect shared data.
Example:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void printMessage(const std::string& message) {
std::lock_guard<std::mutex> lock(mtx); // Locks the mutex
for (int i = 0; i < 5; ++i) {
std::cout << message << std::endl;
}
// The mutex will be automatically unlocked when 'lock' goes out of scope
}
int main() {
std::thread t1(printMessage, "Hello from thread 1");
std::thread t2(printMessage, "Hello from thread 2");
t1.join();
t2.join();
return 0;
}
In this example, two threads are created, and they both call the printMessage
function. We use a std::lock_guard
to lock the mutex mtx
while accessing the shared resource (the std::cout
statement) and automatically unlock it when the function exits.
Handling Data Sharing with Shared Pointers
When multiple threads need to share data and manage its lifetime, std::shared_ptr
can be used to ensure proper memory management.
Example:
#include <iostream>
#include <thread>
#include <memory>
void threadFunction(std::shared_ptr<int> sharedPtr) {
(*sharedPtr)++;
}
int main() {
auto sharedPtr = std::make_shared<int>(42);
std::thread t1(threadFunction, sharedPtr);
std::thread t2(threadFunction, sharedPtr);
t1.join();
t2.join();
std::cout << "Final value: " << *sharedPtr << std::endl;
return 0;
}
In this example, we use std::shared_ptr
to share an integer value between two threads safely. The std::shared_ptr
ensures proper reference counting and memory management.
Async and Future
C++ also provides std::async
and std::future
for handling asynchronous tasks and obtaining results from those tasks.
Example:
#include <iostream>
#include <future>
int addNumbers(int a, int b) {
return a + b;
}
int main() {
// Launch a task asynchronously and get a future object to retrieve the result later
std::future<int> resultFuture = std::async(addNumbers, 10, 20);
// Do other work concurrently...
// Wait for the result and get the value from the future
int result = resultFuture.get();
std::cout << "Result: " << result << std::endl;
return 0;
}
Here, the std::async
function launches the addNumbers
function asynchronously and returns a std::future
object representing the result of the computation. The get()
method on the future is used to obtain the result once it’s ready.
These are some of the basics of multi-threading in Modern C++. Keep in mind that multi-threading introduces complexities, like race conditions and deadlocks, so it’s crucial to handle synchronization and shared resources carefully. Always test your multi-threaded code thoroughly to ensure correctness and performance.