C++11 提供了强大的多线程支持,涵盖了线程的创建、同步、共享数据管理等,极大简化了多线程编程的复杂性。以下是 C++11 中所有基础线程知识、常用函数、应用场景和示例:
1. 基础概念
- 线程:一个程序执行流的最小单位。每个线程都有自己的程序计数器、栈、寄存器等。
- 多线程:程序可以同时执行多个任务,提高了程序的并发能力。
- 并行与并发:并行是多个线程在多个处理器上同时运行;并发是多个线程在同一处理器上通过时间片轮转方式运行。
2. 常用库:<thread>
C++11 引入了 std::thread
类用于创建和管理线程,以及一系列相关的同步和互斥机制。
3. 线程的创建与管理
示例 1:基本线程创建与启动
#include <iostream>
#include <thread>
void threadFunction(int n) {
for (int i = 0; i < n; ++i) {
std::cout << "Thread executing\n";
}
}
int main() {
std::thread t1(threadFunction, 5); // 创建线程并传递参数
t1.join(); // 等待线程完成
return 0;
}
std::thread t1
:创建线程并启动threadFunction
。join()
:阻塞主线程,直到子线程执行完毕。- 注意:如果不调用
join()
或detach()
,程序会抛出异常。
注意事项:
- 线程必须 join 或 detach:线程结束前必须调用
join
或detach
,否则会导致程序崩溃。 - detach():分离线程,使其独立于主线程执行,不再等待它完成。
示例 2:分离线程 detach()
std::thread t2([] { std::cout << "Detached thread running\n"; });
t2.detach(); // 分离线程,主线程不会等待
4. 线程同步与数据共享
在多线程环境下访问共享数据时,可能会导致数据竞争问题,C++11 提供了多种同步机制来防止这些问题。
4.1 互斥锁 (std::mutex
)
用于保护共享数据,保证同一时间只有一个线程能访问资源。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥量
void printMessage(const std::string& msg) {
std::lock_guard<std::mutex> lock(mtx); // 加锁,自动释放
std::cout << msg << std::endl;
}
int main() {
std::thread t1(printMessage, "Hello from thread 1");
std::thread t2(printMessage, "Hello from thread 2");
t1.join();
t2.join();
return 0;
}
std::lock_guard<std::mutex>
:在构造时自动加锁,析构时自动解锁,防止死锁。
4.2 条件变量 (std::condition_variable
)
用于线程间的等待和通知机制。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void printMessage() {
std::unique_lock<std::mutex> lock(mtx); // 加锁
cv.wait(lock, []{ return ready; }); // 等待直到 ready 为 true
std::cout << "Thread executing\n";
}
int main() {
std::thread t(printMessage);
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟工作
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 唤醒等待的线程
t.join();
return 0;
}
wait()
:等待条件变量满足。notify_one()
:通知一个等待的线程。notify_all()
:通知所有等待的线程。
4.3 std::future
与 std::promise
在C++中,std::future
和std::promise
用于实现线程间的异步通信。它们允许一个线程等待另一个线程的结果。std::future
代表一个异步操作的结果,std::promise
用于设置异步操作的结果并将其传递给std::future
。
std::async
示例
std::async
可以启动一个异步任务并返回一个std::future
对象,主线程可以通过future.get()
来等待结果。
#include <iostream>
#include <future>
// 异步计算函数
int calculate(int x) {
return x * x;
}
int main() {
// 异步启动计算任务
std::future<int> result = std::async(calculate, 10);
// 主线程可以继续执行其他任务
// 获取异步任务结果
std::cout << "Result: " << result.get() << std::endl; // 阻塞等待,直到结果可用
return 0;
}
要点解释:
std::async
:启动一个异步任务,返回std::future
对象。任务可以并发执行,结果会被异步计算。future.get()
:阻塞主线程,直到任务完成并返回结果。
std::promise
示例
std::promise
提供了一种主动设置值并将结果传递给std::future
的方式。std::promise
允许一个线程设置异步任务的结果,另一个线程通过std::future
获取结果。
#include <iostream>
#include <thread>
#include <future>
// 线程函数,使用 promise 设置结果
void calculate(std::promise<int>& prom, int x) {
prom.set_value(x * x); // 设置结果
}
int main() {
// 创建一个 promise 对象
std::promise<int> prom;
// 获取与 promise 关联的 future 对象
std::future<int> result = prom.get_future();
// 创建一个线程,传入 promise 对象
std::thread t(calculate, std::ref(prom), 10);
// 获取线程结果
std::cout << "Result: " << result.get() << std::endl;
// 等待线程结束
t.join();
return 0;
}
要点解释:
std::promise<int>
:用于设置结果的类,允许从一个线程将值传递给另一个线程。std::future<int>
:从std::promise
获取,通过future.get()
来获取结果。promise.set_value()
:在执行线程中设置计算结果。
std::async
与 std::promise
对比
std::async
:用于启动异步任务,并返回std::future
,任务结果由系统管理,简单方便。std::promise
:提供了更细粒度的控制,用户可以主动设置结果值,并通过future
获取,可以在复杂的线程间通信中使用。
常见使用场景
std::async
:适用于简单的异步任务,自动管理任务的启动和结果获取。std::promise
:适用于复杂的线程通信场景,例如需要手动控制任务结果的设置,或者在不同线程之间传递异常信息。
注意事项
- 异常处理:如果任务抛出异常,
std::future.get()
会将异常重新抛出。使用std::promise
时,使用set_exception()
可以在future.get()
中获取异常。 - 共享状态:
std::future
和std::promise
之间共享状态,在future.get()
之前必须保证promise.set_value()
已被调用,否则会阻塞。
5.多线程、线程池
1. 多线程原理
1.1 并发与并行
- 并发(Concurrency):在单核或多核处理器上同时管理多个任务的执行。虽然同一时间可能只运行一个任务,但通过快速切换看似多个任务一起进行。
- 并行(Parallelism):在多核处理器上同时执行多个任务,每个核心运行一个任务,因此真正意义上同时执行多个任务。
1.2 多线程的优势
- 提高CPU利用率:在多核处理器中,多线程可以有效利用多个CPU核心,提升并发处理能力。
- 响应性:UI应用可以将繁重的任务放在后台线程中,从而保持界面的响应。
- 任务分割:将复杂的任务分割为多个小任务,并行执行,以提高执行效率。
2. 线程池的设计
线程池(Thread Pool)是一种预先创建一定数量的线程,并重复使用这些线程来执行任务的机制。它的主要优点是避免频繁创建和销毁线程的开销。
2.1 线程池的基本原理
线程池中有以下几个主要元素:
- 任务队列:用来存储需要执行的任务。任务可以是函数、类或者lambda表达式。
- 工作线程:线程池中的线程循环从任务队列中获取任务并执行。
- 任务分配:当有空闲线程时,将任务分配给该线程执行。
- 任务同步:保证多个线程同时访问共享资源时不会产生冲突。
2.2 线程池的Demo
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>
class ThreadPool {
public:
ThreadPool(size_t threads);
~ThreadPool();
// 添加任务到线程池
template<class F, class... Args>
void enqueue(F &&f, Args&&... args);
private:
std::vector<std::thread> workers; // 工作线程
std::queue<std::function<void()>> tasks; // 任务队列
std::mutex queue_mutex; // 同步任务队列
std::condition_variable condition;
bool stop; // 控制线程池是否停止
// 工作线程执行的函数
void worker();
};
ThreadPool::ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back(&ThreadPool::worker, this); // 创建工作线程
}
}
ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all(); // 通知所有线程结束
for (std::thread &worker : workers) {
worker.join(); // 等待所有线程结束
}
}
void ThreadPool::worker() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task(); // 执行任务
}
}
template<class F, class... Args>
void ThreadPool::enqueue(F &&f, Args&&... args) {
auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace([task]() { task(); });
}
condition.notify_one(); // 通知一个线程处理任务
}
// 测试线程池
void printMessage(int id) {
std::cout << "Thread " << id << " is working." << std::endl;
}
int main() {
ThreadPool pool(4); // 创建一个有4个线程的线程池
for (int i = 0; i < 10; ++i) {
pool.enqueue(printMessage, i); // 添加任务
}
return 0;
}
2.3 线程池设计要点
- 任务队列同步:需要使用
std::mutex
来保护任务队列的同步访问,避免多个线程同时操作任务队列时发生竞争条件。 - 条件变量:使用
std::condition_variable
来通知线程有任务可以执行,避免线程忙等。 - 线程停止:在析构函数中需要等待所有线程完成后才能销毁线程池,确保程序的正常退出。
3. 并发控制
在多线程环境中,必须处理多个线程对共享资源的访问问题,常见的控制方式包括互斥锁、条件变量和读写锁等。
4. 线程安全的设计模式
4.1 生产者-消费者模式
生产者-消费者模式是多线程编程中非常常见的模式,用于多个线程生产和消费共享资源。
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; ++i) {
std::lock_guard<std::mutex> lock(mtx);
dataQueue.push(i);
cv.notify_one(); // 通知消费者有数据
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !dataQueue.empty(); }); // 等待数据
int data = dataQueue.front();
dataQueue.pop();
std::cout << "Consumed: " << data << std::endl;
if (data == 9) break; // 结束消费
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
6. 注意事项与常见问题
- 数据竞争(Data Race):多个线程同时读写同一块内存时可能产生不可预测的结果,必须通过锁机制避免。
- 死锁(Deadlock):多个线程同时等待对方持有的锁时会发生死锁,避免死锁可以通过一致的锁顺序或者使用
std::lock
函数。 - 忙等待(Busy Waiting):线程不停地检查某个条件是否满足,这样会浪费CPU资源,应该使用
std::condition_variable
来实现等待。