Threads and Concurrency Programming in C++11
Introduction
In modern software development, with the prevalence of multi-core processors, it has become increasingly important to write programs that can effectively utilize multi-core features. C++11 introduces built-in support for multi-threading and concurrency programming, making it easier for us to create and manage threads. This article will gradually introduce threads and concurrency programming in C++11, including how to create and use threads, the use of mutexes, and basic concepts such as condition variables, along with detailed demonstrations through example code.
1. Creating and Managing Threads
1.1 Using the std::thread Class
In C++11, new threads can be created using the <span>std::thread</span>
class. Below is a simple example that demonstrates how to start a new thread to execute a function.
#include <iostream>
#include <thread>
void hello() { std::cout << "Hello from thread!" << std::endl;}
int main() { // Create a new thread std::thread t(hello);
// Ensure the main program waits for the child process to finish t.join();
return 0;
}
Explanation:
<span>std::thread t(hello);</span>
: Here, we define a new thread named<span>t</span>
and pass the function<span>hello</span>
to it.<span>t.join();</span>
: Calling this method makes the main program wait for<span>t</span>
to complete. If this method is not called, the main program may exit before<span>t</span>
finishes executing, leading to undefined behavior.
1.2 Passing Parameters to Threads
Our function can also accept parameters. As shown below, it is easy to pass parameters to the newly created thread.
#include <iostream>
#include <thread>
void printNumber(int n) { std::cout << "Thread number: " << n << std::endl;}
int main() { for (int i = 0; i < 5; ++i) { // Pass different parameters to each new thread std::thread t(printNumber, i); t.join(); // Wait for the current job to finish before starting the next job }
return 0;
}
2. Using Mutexes to Protect Shared Data
When multiple threads access shared resources simultaneously, it is necessary to ensure data consistency. In C++11, we can use <span>std::mutex</span>
to implement mutual exclusion control to avoid race conditions.
Example:
#include <iostream>
#include <thread>
#include <mutex>
// Declare global variable and mutex
int counter = 0;
std::mutex mtx;
void incrementCounter() { for (int i = 0; i < 10000; ++i) { mtx.lock(); // Lock for safety ++counter; // Safely access shared data mtx.unlock(); // Unlock }}
int main() { std::thread t1(incrementCounter); std::thread t2(incrementCounter);
t1.join(); t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
Output Analysis:
Unprotected data may lead to uncertainty in the final result, as shown in the above code snippet where running without locks may result in a final count less than 20000. However, by correctly using <span>mtx.lock()</span>
, we can ensure that only one thread can successfully modify the counter at a time, while other parallel threads are blocked, resulting in a predictable and consistent output of 20000.
##3. Condition Variables—Coordinating Multiple Producers and Consumers
Condition variables allow us to effectively control communication between multiple processes suitable for working patterns, such as the producer-consumer problem.
Example:
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
// Define buffer and related information
std::queue<int> buffer;
const unsigned int maxBufferSize = 5;
std::mutex mtx; // Mutex object to protect buffer.
std::condition_variable cv; // Used to coordinate and notify consumers.
void producer() { for(int i=0; i<10; i++) { { std::unique_lock<std::mutex> lck(mtx);
while(buffer.size() == maxBufferSize) { cv.wait(lck); // Wait if buffer is full; }
buffer.push(i); std::cout << "Producer produced: " << i << std::endl;
} cv.notify_all(); // Notify consumers. }}
static void consumer() { for(int j=0; j<10; j++) { { std::unique_lock<std::mutex> lock(mtx);
while(buffer.empty()) { cv.wait(lock); // Wait if buffer is empty. }
std::cout << "Consumer consumed: " << buffer.front() << std::endl; buffer.pop();
} } }
int main() { std::thread prod(producer); std::thread cons(consumer);
prod.join(); cons.join();
return 0;}
Conclusion:
As seen above, when the product is full, using condition variables allows us to wait for instructions to continue production, maintaining a balance between supply and demand. This helps to control excess supply and provides timely input services, addressing negative phenomena in networked cultural clusters and forming commercial flow patterns. We hope this article helps you understand the complete model to promote equal development and share the joy of life.
Engaging in discussions and sharing experiences can help build a collaborative atmosphere, regardless of how many designs are involved. We hope to assist you in mastering the context and essence of these mechanisms, enhancing your understanding and learning process.