第一部分:thread概念
一、引言
C++11 引入了多线程编程的支持,主要是通过 <thread> 头文件中的 std::thread 类来实现的。这一特性极大地增强了 C++ 应用程序的并发处理能力,允许开发者编写能够同时执行多个任务的应用程序。
二、基本概念
std::thread 是一个表示执行线程的类,它提供了创建和管理线程的基本功能。每个 std::thread 对象都与一个执行线程相关联。当 std::thread 对象被创建时,它会启动一个新的执行线程,该线程会执行传递给 std::thread 构造函数的函数或可调用对象。
三、构造函数
std::thread 类有几个构造函数,但最常用的构造函数接受一个可调用对象(如函数指针、lambda 表达式、函数对象、绑定表达式等)作为参数,并可选地接受一个或多个传递给该可调用对象的参数。
四、示例
cpp
#include <iostream>
#include <thread>
void threadFunction(int n) {
std::cout << "Thread function called with n = " << n << std::endl;
}
int main() {
std::thread t(threadFunction, 42); // 创建一个新线程,运行 threadFunction(42)
t.join(); // 等待线程结束
return 0;
}
五、成员函数
- join(): 阻塞当前线程,直到与之关联的线程执行完毕。
- detach(): 将线程与 std::thread 对象分离,允许线程独立执行。一旦分离,std::thread 对象将不再拥有任何线程,也不能再次与任何线程关联或加入。
- get_id(): 返回线程的标识符(std::thread::id 类型)。
- joinable(): 检查线程是否可被 join()。如果线程已经执行完毕或被分离,则返回 false;否则返回 true。
- swap(std::thread& other): 交换两个 std::thread 对象的线程所有权。
六、注意事项
- 资源管理:如果不调用 join() 或 detach(),std::thread 对象的析构函数会调用 std::terminate(),因为此时线程仍在运行且没有明确的处理方式。
- 线程安全:多线程程序必须考虑数据竞争和同步问题,可能需要使用互斥锁(std::mutex)、条件变量(std::condition_variable)等同步机制。
- 性能考量:线程的创建和销毁是昂贵的操作,应尽量减少不必要的线程创建和销毁。
- 异常安全:如果线程函数抛出异常且未被捕获,则标准没有规定具体的行为。因此,在线程函数中应妥善处理异常。
七、多个thread对象
cpp
#include <iostream>
#include <thread>
#include <vector>
void doWork(int id) {
std::cout << "Thread " << id << " is running" << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(doWork, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
这个示例展示了如何创建多个线程来并行执行相同的任务。每个线程都被加入到 std::vector<std::thread> 容器中,并在之后通过循环调用 join() 等待所有线程完成。
第二部分:多线程编程相关的特性
C++11 中的 std::thread 以及其他与多线程编程相关的特性
一、线程同步
在多线程编程中,线程同步是一个重要的概念,它用于控制多个线程之间的执行顺序,以避免数据竞争和其他并发问题。C++11 提供了几种线程同步机制:
1. 互斥锁(Mutexes):
std::mutex 是最基本的同步原语,它提供了一种保护共享数据免受多个线程同时访问的机制。互斥锁通过锁定和解锁操作来确保在任一时刻只有一个线程可以访问受保护的数据。
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void increment() {
mtx.lock(); // 锁定互斥锁
++shared_data;
mtx.unlock(); // 解锁互斥锁
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Shared data: " << shared_data << std::endl;
return 0;
}
注意:频繁地锁定和解锁互斥锁可能会降低性能,因为线程需要等待锁变为可用。
2. 锁保护(Lock Guards):
std::lock_guard 是一个作用域锁定的封装,它会在构造时自动锁定互斥锁,并在作用域结束时自动解锁。这有助于减少因忘记释放锁而导致的死锁问题。
cpp
#include <mutex>
std::mutex mtx;
void safe_increment() {
std::lock_guard<std::mutex> guard(mtx);
++shared_data; // mtx 已被锁定
}
3. 条件变量(Condition Variables):
std::condition_variable 用于阻塞一个或多个线程,直到接收到另一个线程的通知。它通常与互斥锁一起使用,以安全地等待某个条件变为真。
cpp
#include <condition_variable>
#include <mutex>
#include <thread>
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); // 等待 ready 变为 true
std::cout << "Thread " << id << '\n';
}
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 << "10 threads ready to race...\n";
go(); // 允许 10 个线程继续执行
for (auto& th : threads) th.join();
return 0;
}
4. 线程局部存储(Thread-Local Storage, TLS)
C++11 引入了线程局部存储的概念,允许变量在每个线程中都有自己的实例。这可以通过 thread_local 关键字来实现。
cpp
#include <iostream>
#include <thread>
thread_local int tls_counter = 0;
void print_and_increment() {
++tls_counter;
std::cout << "tls_counter = " << tls_counter << std::endl;
}
int main() {
std::thread t1(print_and_increment);
std::thread t2(print_and_increment);
t1.join();
t2.join();
// 主线程也有自己的 tls_counter 实例
print_and_increment();
return 0;
}
在这个例子中,每个线程(包括主线程)都有自己的 tls_counter 实例,并且它们之间的值互不影响。
二、小结
C++11 通过 std::thread 和其他相关的类和函数(如 std::mutex、std::lock_guard、std::condition_variable 和 thread_local)为多线程编程提供了强大的支持。然而,编写正确的多线程代码仍然是一个挑战,需要开发者深入理解线程同步和并发编程的概念。