C++ 中的多线程与并发编程:从基础到进阶
随着现代计算机硬件的多核发展,多线程和并发编程变得越来越重要。C++11 引入了对多线程的原生支持,提供了强大的线程管理、同步机制和并发工具,使得在 C++ 中编写高效的多线程程序变得更加容易。本文将从基础到进阶,带你深入理解 C++ 多线程编程,包括线程的创建与管理、同步机制、并发算法等。
一、C++ 多线程基础
在 C++11 中,std::thread
是实现多线程的核心类。通过 std::thread
,我们可以轻松地创建和管理线程。
1.1、创建线程
通过 std::thread
类,创建一个新的线程并执行指定的函数。线程的生命周期由 std::thread
对象的构造和销毁管理。
示例代码:
#include <iostream>
#include <thread>
// 线程执行的函数
void printMessage() {
std::cout << "Hello from the thread!" << std::endl;
}
int main() {
// 创建一个线程并执行 printMessage 函数
std::thread t(printMessage);
// 等待线程完成
t.join();
std::cout << "Main thread finished." << std::endl;
return 0;
}
输出:
Hello from the thread!
Main thread finished.
在这个例子中,我们创建了一个线程 t
来执行 printMessage
函数。通过调用 join()
方法,主线程会等待 t
线程完成后再继续执行。
1.2、线程的分离
std::thread
还提供了 detach()
方法,用于将线程与主线程分离,使得线程独立执行。分离后的线程无法通过 join()
等待其完成,因此主线程不再负责线程的生命周期。
示例代码:
#include <iostream>
#include <thread>
// 线程执行的函数
void printMessage() {
std::cout << "Hello from the detached thread!" << std::endl;
}
int main() {
// 创建并分离一个线程
std::thread t(printMessage);
t.detach(); // 分离线程
std::cout << "Main thread finished." << std::endl;
// 主线程执行完后,分离线程会继续执行,直到完成
std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待子线程执行完
return 0;
}
输出:
Main thread finished.
Hello from the detached thread!
在这个例子中,detach()
方法将线程从主线程分离,允许它独立执行。然而,需要注意的是,分离的线程必须在主线程结束前完成,否则它会变成一个“孤儿线程”,并可能导致程序崩溃。
二、线程同步与共享资源
多线程编程中,多个线程可能会访问共享资源,导致竞争条件(race condition)和数据不一致问题。为了保证线程安全,我们需要使用同步机制来保护共享资源。
2.1、互斥锁(std::mutex
)
std::mutex
是 C++11 引入的一个同步原语,用于防止多个线程同时访问共享资源。通过锁定互斥量,我们可以确保只有一个线程能够访问共享资源。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义一个互斥锁
int sharedData = 0; // 共享资源
// 线程执行的函数
void increment() {
std::lock_guard<std::mutex> lock(mtx); // 加锁
++sharedData;
std::cout << "Shared data: " << sharedData << std::endl;
}
int main() {
// 创建多个线程同时访问共享资源
std::thread t1(increment);
std::thread t2(increment);
std::thread t3(increment);
// 等待线程完成
t1.join();
t2.join();
t3.join();
return 0;
}
输出:
Shared data: 1
Shared data: 2
Shared data: 3
在这个例子中,std::lock_guard<std::mutex>
用于确保对 sharedData
的访问是线程安全的。每个线程在访问共享资源之前都会加锁,确保只有一个线程可以修改 sharedData
。
2.2、死锁与避免
死锁(Deadlock)是多线程编程中常见的问题,发生在两个或多个线程因为互相等待对方释放资源而永远阻塞。为了避免死锁,可以采取以下几种策略:
- 加锁顺序:确保所有线程以相同的顺序请求多个锁。
- 使用
std::lock
:它能防止因加锁顺序不同而导致的死锁。
示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void task1() {
std::lock(mtx1, mtx2); // 同时锁定两个互斥锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Task 1 completed\n";
}
void task2() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Task 2 completed\n";
}
int main() {
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
return 0;
}
输出:
Task 1 completed
Task 2 completed
通过 std::lock
,我们确保了两个互斥锁 mtx1
和 mtx2
被同时锁定,避免了死锁。
三、并发算法与 std::async
C++11 引入了 std::async
和 std::future
,使得在多线程环境中调用异步任务变得非常方便。std::async
用于启动异步任务,并返回一个 std::future
对象,用来获取任务的结果。
3.1、std::async
示例
#include <iostream>
#include <thread>
#include <future>
// 异步任务
int calculateSquare(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟长时间计算
return x * x;
}
int main() {
// 启动异步任务
std::future<int> result = std::async(std::launch::async, calculateSquare, 10);
// 其他工作(模拟主线程做其他事情)
std::cout << "Main thread is doing other work..." << std::endl;
// 获取异步任务结果
std::cout << "Square of 10 is: " << result.get() << std::endl;
return 0;
}
输出:
Main thread is doing other work...
Square of 10 is: 100
在这个例子中,std::async
启动了一个异步任务来计算 10 的平方,而主线程则可以继续执行其他工作。当我们调用 result.get()
时,主线程会等待异步任务完成并获取结果。
四、并发容器与算法
C++17 引入了一些新的并发数据结构和算法,如 std::shared_mutex
、std::scoped_lock
等,进一步简化了并发编程的复杂度。
4.1、std::shared_mutex
与读写锁
std::shared_mutex
允许多个线程并发地读取共享资源,同时确保在写操作时对资源的独占访问。
示例代码:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
std::shared_mutex rw_mutex;
std::vector<int> data;
void readData() {
std::shared_lock<std::shared_mutex> lock(rw_mutex); // 共享锁
std::cout << "Reading data: ";
for (int val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
void writeData(int val) {
std::unique_lock<std::shared_mutex> lock(rw_mutex); // 独占锁
data.push_back(val);
std::cout << "Written: " << val << std::endl;
}
int main() {
std::thread t1(writeData, 10);
std::thread t2(writeData, 20);
std::thread t3(readData);
std::thread t4(readData);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
输出:
Written: 10
Written: 20
Reading data: 10 20
Reading data: 10 20
在这个例子中,std::shared_mutex
允许多个线程同时读取数据,但在写入数据时,只有一个线程能够获得独占锁,从而确保数据的一致性。
五、总结
C++ 的多线程编程为我们提供了强大的并发控制和线程管理能力。通过使用 std::thread
、std::mutex
、std::async
等工具,我们能够高效地创建和管理线程,同时保证数据的一致性和安全性。无论是简单的多线程任务,还是复杂的并发算法,C++ 都能为我们提供全面的支持。
在实际开发中,掌握 C++ 多线程编程不仅能提升程序性能,还能帮助你更好地利用现代硬件资源。希望本文能够帮助你在 C++ 多线程编程的道路上迈出坚实的一步。