C++多线程编程
概述
多线程编程允许在同一个程序中同时执行多个任务,从而提高程序的效率,特别是在处理 I/O 密集型或计算密集型任务时。C++11 引入了标准库中的多线程支持,使得 C++ 开发者能够更加方便地进行多线程编程。
线程基础
线程是程序中执行的最小单位,每个线程有自己的程序计数器、堆栈和局部变量。C++ 的 库提供了对线程的支持,允许我们在程序中启动多个并行的线程。
线程的创建与管理
要创建一个线程,首先需要包含 头文件。通过 std::thread 类可以创建线程。
创建线程
cpp
#include <iostream>
#include <thread>
void print_hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(print_hello); // 创建一个线程,调用 print_hello 函数
t.join(); // 等待线程执行完成
return 0;
}
解释:
- std::thread 构造函数接收一个函数作为参数,该函数将在新线程中执行。
- t.join() 会阻塞主线程,直到 t 所表示的线程执行完成。
- join 是线程同步的一种方式,确保主线程等待子线程完成后再继续执行。
多个线程
可以创建多个线程来执行不同的任务。每个线程都会执行一个独立的任务。
cpp
#include <iostream>
#include <thread>
void print_hello() {
std::cout << "Hello from thread!" << std::endl;
}
void print_world() {
std::cout << "World from thread!" << std::endl;
}
int main() {
std::thread t1(print_hello);
std::thread t2(print_world);
t1.join();
t2.join();
return 0;
}
- t1 和 t2 是两个不同的线程,它们分别执行 print_hello 和 print_world 函数。
- join() 确保在程序结束之前,所有线程都完成。
线程的分离
除了 join,还有 detach 函数。使用 detach 后,线程将独立于主线程执行,它不会被等待完成,主线程可以继续执行。
cpp
#include <iostream>
#include <thread>
void print_message() {
std::cout << "Message from detached thread!" << std::endl;
}
int main() {
std::thread t(print_message);
t.detach(); // 分离线程,主线程继续执行
std::cout << "Main thread continues!" << std::endl;
// 注意:如果主线程结束,分离线程可能未执行完成
return 0;
}
注意:
- 使用 detach 后,线程在后台执行,主线程可以退出,不会等待它的完成。若主线程结束时子线程仍在运行,则可能会出现未定义的行为,因此要小心使用 detach。
线程同步
多线程编程常常面临多个线程共享资源的情况。如果多个线程同时访问和修改共享数据,就会引发数据竞争问题。这时,需要对共享资源进行同步。
互斥量(Mutex)
互斥量是保护共享资源的一种机制,它确保同一时刻只有一个线程能访问共享资源。
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥量
void print_message(const std::string &message) {
mtx.lock(); // 上锁
std::cout << message << std::endl;
mtx.unlock(); // 解锁
}
int main() {
std::thread t1(print_message, "Hello from thread 1");
std::thread t2(print_message, "Hello from thread 2");
t1.join();
t2.join();
return 0;
}
- std::mutex mtx 定义了一个互斥量,用来保护共享资源(此处是 std::cout)。
- mtx.lock() 获取锁,确保当前线程独占对 cout 的访问。
- mtx.unlock() 释放锁,允许其他线程获取锁。
std::lock_guard 和 std::unique_lock
为了避免手动调用 lock() 和 unlock(),C++ 提供了 std::lock_guard 和 std::unique_lock,它们在作用域结束时自动释放锁,避免忘记解锁。
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥量
void print_message(const std::string &message) {
std::lock_guard<std::mutex> lock(mtx); // 上锁
std::cout << message << std::endl;
// 自动解锁,当 lock_guard 对象超出作用域时
}
int main() {
std::thread t1(print_message, "Hello from thread 1");
std::thread t2(print_message, "Hello from thread 2");
t1.join();
t2.join();
return 0;
}
- std::lock_guard 自动获取和释放锁,使代码更加简洁安全。
std::condition_variable
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); // 等待条件满足
std::cout << "Thread " << id << std::endl;
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true; // 改变条件
cv.notify_all(); // 通知所有线程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "Waiting for threads to be ready..." << std::endl;
go(); // 通知线程
for (auto& th : threads) th.join();
return 0;
}
- cv.wait(lck) 会使当前线程等待直到 ready 为 true。
- cv.notify_all() 会通知所有等待的线程继续执行。
原子操作
原子操作是不会被中断的操作,它对于多线程同步非常有用。在 C++11 中,std::atomic 提供了对基本数据类型的原子操作支持。
cpp
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 1000; ++i)
++counter; // 原子增加
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter.load() << std::endl;
return 0;
}
- std::atomic 确保对 counter 的操作是原子的,不会被多个线程同时修改,避免了数据竞争。