【C++多线程编程】:安全使用std::list的并发编程技巧!
发布时间: 2024-10-23 05:29:02 阅读量: 1 订阅数: 5
![【C++多线程编程】:安全使用std::list的并发编程技巧!](https://nixiz.github.io/yazilim-notlari/assets/img/thread_safe_banner_2.png)
# 1. C++多线程编程概述
## 1.1 多线程编程的发展简史
在计算机科学中,多线程是一种允许程序同时执行两个或两个以上线程(也就是程序的执行路径)的技术,使得任务能够并行处理,提高资源的利用率和程序的响应速度。从早期的单核处理器到现在多核处理器的普及,多线程编程逐渐成为一种必要,尤其是在需要处理并发任务的服务器端应用程序中。
## 1.2 C++多线程编程的重要性
C++作为一种高性能的编程语言,提供了丰富的多线程编程支持。随着C++11标准的引入,C++的多线程支持得到了大幅增强,包括了`<thread>`, `<mutex>`, `<condition_variable>`等在内的多个并发相关库的加入。这使得C++程序员可以在语言层面上直接利用多线程的优势,编写出高效率、高性能的软件。
## 1.3 多线程编程面临的挑战
尽管多线程编程能够带来性能上的提升,但同时也引入了诸如数据竞争、死锁、资源锁等复杂的问题。开发者必须谨慎地管理线程的创建、执行和同步,确保线程安全并充分利用多核处理器的能力。本章接下来的内容将概述C++多线程编程的基础知识和概念,为接下来深入探讨各种多线程编程技术和最佳实践打下基础。
# 2. C++11多线程基础
C++11标准引入的多线程支持为我们带来了在C++语言中实现并发编程的全新途径。本章将详细介绍C++11中关于多线程编程的基础知识,包括线程的创建与管理、互斥锁的使用、条件变量的高级应用等。我们将通过代码示例、流程图和表格等手段,帮助读者深入理解C++11多线程编程的核心概念和技术细节。
## 2.1 线程的创建与管理
### 2.1.1 std::thread的基本使用
在C++11中,std::thread是线程管理的主要类。通过它,我们可以创建线程,传递参数给线程函数,以及获取线程ID。创建一个线程的基本步骤如下:
```cpp
#include <iostream>
#include <thread>
void threadFunction() {
std::cout << "Thread is running." << std::endl;
}
int main() {
std::thread t(threadFunction);
t.join(); // 等待线程完成
return 0;
}
```
在上面的代码中,我们定义了一个`threadFunction`函数,然后使用`std::thread`对象`t`来启动一个新的线程。调用`t.join()`是为了让主线程等待新创建的线程完成,保证程序不会在子线程执行完毕前退出。
### 2.1.2 线程的启动和同步
线程启动后,我们需要对线程进行同步,确保线程间的正确执行顺序。C++11提供了多种同步机制,其中最常用的是`std::mutex`和`std::condition_variable`。下面是一个简单的示例来展示如何使用它们进行线程同步:
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lck(mtx);
while (!ready) {
cv.wait(lck);
}
// 打印线程ID
std::cout << "Thread " << id << " is running." << std::endl;
}
int main() {
std::thread threads[10];
// 创建10个线程
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
// 启动所有线程
{
std::lock_guard<std::mutex> lck(mtx);
ready = true;
}
// 通知所有线程继续
cv.notify_all();
// 等待所有线程完成
for (auto &th : threads) th.join();
return 0;
}
```
在上述代码中,我们使用`std::lock_guard`来保护一个共享资源`ready`,并在其变为`true`之前让所有线程处于等待状态。`std::condition_variable`的`notify_all`方法用于唤醒所有等待的线程。
## 2.2 互斥锁与线程安全
### 2.2.1 std::mutex的使用
在多线程编程中,互斥锁是一种用于防止多个线程同时访问共享资源的同步机制。C++11提供了`std::mutex`,以及它的几种变体,包括`std::timed_mutex`和`std::recursive_mutex`等。以下是使用`std::mutex`来保护共享数据的示例:
```cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_var = 0;
void increment(int num) {
for (int i = 0; i < num; ++i) {
mtx.lock();
++shared_var;
mtx.unlock();
}
}
int main() {
std::thread t1(increment, 10000);
std::thread t2(increment, 10000);
t1.join();
t2.join();
std::cout << "Final value of shared_var is: " << shared_var << std::endl;
return 0;
}
```
在这个示例中,`increment`函数对`shared_var`进行增加操作,由于使用了`std::mutex`进行加锁保护,因此即使两个线程同时运行,`shared_var`的值也是线程安全的。
### 2.2.2 死锁的避免与处理
死锁是在并发程序设计中遇到的常见问题,它发生在多个线程因为互相等待对方释放资源而无限期地阻塞下去的情况。为了避免死锁,我们需要合理地设计锁的获取顺序、避免不必要的嵌套锁,以及使用超时机制等。下面提供了一个简单的例子来展示避免死锁的情况:
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mtx_a, mtx_b;
void func() {
std::unique_lock<std::mutex> lck_a(mtx_a, std::defer_lock); // 不立即加锁
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟长时间操作
std::unique_lock<std::mutex> lck_b(mtx_b, std::defer_lock); // 不立即加锁
// 同时加锁两个互斥锁
std::lock(lck_a, lck_b);
// 执行需要互斥的操作...
}
int main() {
std::thread t(func);
t.join();
return 0;
}
```
在这个例子中,我们使用了`std::defer_lock`参数和`std::lock`函数,先构造了两个`std::unique_lock`对象,但没有立即加锁,而是在需要时一起加锁,这样可以避免死锁的发生。
## 2.3 条件变量的高级应用
### 2.3.1 std::condition_variable的使用
`std::condition_variable`是用来实现线程间协调的一种同步机制,它可以阻塞一个线程,直到有信号(signal)或事件(event)发生。这对于实现生产者-消费者模型等场景特别有用。我们可以通过以下代码来了解如何使用它:
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
int cargo = 0;
void consumer() {
while (true) {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{return cargo != 0;});
std::cout << "Consumed: " << cargo << std::endl;
cargo = 0;
lck.unlock();
cv.notify_one(); // 通知一个等待的线程
}
}
void producer(int n) {
for (int i = 0; i < n; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时操作
std::lock_guard<std::mutex> lck(mtx);
++cargo;
std::cout << "Produced: " << cargo << std::endl;
cv.notify_one(); // 通知一个等待的线程
}
}
int main() {
std::thread t1(consumer);
std::thread t2(producer, 5);
t1.join();
t2.join();
return 0;
}
```
在上述代码中,`consumer`函数会等待`cargo`变量不为零,表示有产品可供消费。`producer`函数生产产品,并在每次生产后通过`notify_one`方法通知消费者。这展示了如何使用`std::condition_variable`同步生产者和消费者的行为。
### 2.3.2 等待与通知机制
`std::condition_variable`提供了等待和通知的机制。等待通常与某种条件相关联,只有条件满足时线程才会继续执行。通知是当特定条件发生变化时唤醒等待的线程。以下是一个简化的示例:
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
int i = 0;
std::mutex mtx;
std::condition_variable cv;
void wait_for_even() {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{ return i % 2 == 0; });
std::cout << "Wait_for_even: " << i << std::endl;
}
void signal_even() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::lock_guard<std::mutex> lck(mtx);
i++;
cv.notify_one();
}
int main() {
std::thread t1(wait_for_even);
std::thread t2(signal_even);
t1.join();
t2.join();
return 0;
}
```
上述代码模拟了一个简单的场景:`wait_for_even`函数中的线程等待`i`的值为偶数,而`signal_even`函数修改`i`的值并通知等待的线程。
以上是本章关于C++11多线程基础的内容,通过这些基本的使用和同步机制,读者可以构建起对C++11多线程编程的认识框架,并为进一步深入学习打下坚实的基础。接下来的章节将继续介绍如何在实践中使用这些基础概念来解决实际问题。
# 3. std::list并发访问控
0
0