C++ 多线程编程允许程序并行执行多个任务,这样可以有效地利用多核处理器,提高程序的性能和响应速度。C++11 引入了对多线程的标准支持,使得线程创建、同步和管理变得更加简单和直观。本文将详细介绍 C++ 中的多线程编程。
1. C++ 多线程基础
C++11 引入了 <thread> 头文件,它提供了创建和管理线程的标准接口。要使用多线程,需要包含 <thread> 头文件。
1.1 创建线程
C++ 使用 std::thread 类来表示和管理线程。线程的创建非常简单,创建线程时可以通过传递一个可调用对象(如函数、lambda 表达式、成员函数等)来启动线程。
#include <iostream>
#include <thread>
void print_hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
// 创建并启动线程
std::thread t(print_hello);
// 等待线程结束
t.join(); // 阻塞主线程,直到 t 线程执行完毕
return 0;
}
说明:
std::thread t(print_hello);创建一个新线程并执行print_hello函数。t.join()阻塞主线程,直到t线程执行完毕。如果不调用join()或detach(),程序会终止时抛出异常。
1.2 join() 与 detach()
join():阻塞当前线程,等待线程执行完毕后再继续执行。通常在主线程中调用join()来等待子线程的完成。detach():使线程脱离当前线程,独立执行。脱离的线程在完成后不会影响主线程的结束,因此需要注意其生命周期。
#include <iostream>
#include <thread>
void print_hello() {
std::cout << "Hello from detached thread!" << std::endl;
}
int main() {
std::thread t(print_hello);
t.detach(); // 脱离线程,不等待它完成
std::cout << "Main thread continues execution." << std::endl;
// 程序结束时,t 线程可能尚未执行完毕
return 0;
}
说明:
t.detach()会使t线程脱离主线程。脱离的线程在完成后不再需要主线程等待。
2. 线程参数传递
线程函数可以接受参数,这些参数可以通过值传递或引用传递。
2.1 值传递
#include <iostream>
#include <thread>
void print_number(int n) {
std::cout << "Number: " << n << std::endl;
}
int main() {
int number = 5;
std::thread t(print_number, number); // 值传递参数
t.join();
return 0;
}
说明:
- 传递参数时,C++ 会自动将参数进行复制,即值传递。
2.2 引用传递
若要传递引用,可以使用 std::ref 来避免复制。
#include <iostream>
#include <thread>
void increment(int& n) {
++n;
std::cout << "Incremented: " << n << std::endl;
}
int main() {
int number = 5;
std::thread t(increment, std::ref(number)); // 引用传递
t.join();
std::cout << "Main number: " << number << std::endl;
return 0;
}
说明:
std::ref(number)传递的是number的引用,而不是它的副本。这样increment函数可以直接修改原始数据。
3. 线程同步
在多线程环境中,多个线程可能会并发访问共享资源,造成数据竞态问题。C++ 提供了多种同步机制来解决这个问题,如互斥量(mutex)和条件变量(condition_variable)。
3.1 互斥量(mutex)
互斥量用于保证同一时刻只有一个线程访问共享资源。C++11 引入了 std::mutex,它提供了基本的互斥操作。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 声明一个互斥量
void print_hello() {
mtx.lock(); // 上锁
std::cout << "Hello from thread!" << std::endl;
mtx.unlock(); // 解锁
}
int main() {
std::thread t1(print_hello);
std::thread t2(print_hello);
t1.join();
t2.join();
return 0;
}
说明:
std::mutex mtx;声明一个互斥量对象。mtx.lock()上锁,保证当前线程独占对资源的访问。mtx.unlock()解锁,允许其他线程访问。
3.2 std::lock_guard 和 std::unique_lock
std::lock_guard 是一种简化的锁机制,它会在作用域结束时自动释放锁,从而避免了手动调用 unlock()。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_hello() {
std::lock_guard<std::mutex> lock(mtx); // 自动上锁和解锁
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t1(print_hello);
std::thread t2(print_hello);
t1.join();
t2.join();
return 0;
}
说明:
std::lock_guard在构造时上锁,在析构时自动解锁,从而避免忘记解锁的错误。
3.3 条件变量(std::condition_variable)
条件变量用于实现线程之间的通信。一个线程可以等待条件变量,另一个线程可以通过通知条件变量来唤醒等待的线程。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_hello() {
std::unique_lock<std::mutex> lock(mtx);
while (!ready) {
cv.wait(lock); // 等待条件变量
}
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t1(print_hello);
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true; // 设置条件
}
cv.notify_one(); // 通知一个线程
t1.join();
return 0;
}
说明:
cv.wait(lock)会让线程在条件变量上等待,直到另一个线程调用cv.notify_one()或cv.notify_all()。std::unique_lock用于与条件变量一起工作,它可以在wait和notify过程中自动释放和获取锁。
4. 线程安全与并发问题
当多个线程并发访问共享资源时,必须确保对共享资源的访问是线程安全的。使用互斥量、条件变量、读写锁等同步机制可以避免数据竞争和死锁等问题。
- 死锁:当多个线程互相等待对方释放资源时,程序会进入死锁状态。要避免死锁,可以通过设计合理的锁顺序、避免嵌套锁等方法来防止。
- 数据竞争:多个线程同时修改共享变量而没有同步保护,可能导致不一致的结果。通过使用互斥量等同步机制可以避免数据竞争。
5. 小结
C++ 多线程编程提供了强大的工具来实现并发和并行处理。通过使用 std::thread 创建和管理线程,结合互斥量、条件变量等同步机制,可以编写高效且线程安全的程序。在进行多线程编程时,必须特别注意线程同步和共享资源访问的安全性。