在 C++ 编程领域,多线程编程是提升程序性能和实现高效并发的关键手段。它允许程序同时执行多个任务,充分利用多核处理器的优势,在诸如游戏开发、服务器端编程、数据分析等诸多场景中发挥着重要作用。接下来,让我们一同学习线程的std::thread
的相关知识。
一、线程的创建与启动
- 函数指针作为线程入口
通过std::thread
构造函数传递函数指针或可调用对象,在传递参数时,默认是按值传递的。若需要传递引用类型的参数,则必须使用std::ref或std::cref
cpp
#include <thread>
void func(int x) { /* ... */ }
int main() {
int num = 42;
std::thread t(func, std::ref(num)); // 按引用传递
t.join();
}
- 类成员函数作为线程入口
需要传递成员函数的指针、类对象的指针以及成员函数所需的参数
cpp
#include <iostream>
#include <thread>
class MyClass {
public:
void work(int x) {
std::cout << "Working with value: " << x << std::endl;
}
};
int main() {
MyClass obj;
int param = 100;
// 创建线程,以类的成员函数作为入口
std::thread t(&MyClass::work, &obj, param);
t.join();
return 0;
}
- 以Lambda表达式作为线程入口
cpp
#include <iostream>
#include <thread>
int main() {
int data = 20;
// 使用 Lambda 表达式创建线程
std::thread t([data]() {
std::cout << "Data from Lambda thread: " << data << std::endl;
});
t.join();
return 0;
}
- 函数对象作为入口
需重载 operator(),对象会被复制到线程存储空间
cpp
#include<iostream>
#include<thread>
struct Task{
operator ()(){
std::cout<<"Task executed"<<endl;
}
}
int main(){
Task task;
std::thread t(task); //调用task的operator
t.join;
return 0;
}
二、线程生命周期管理
线程在 std::thread
对象构造时立即启动,而非调用 join()
时才开始执行
1. join()
join() 方法会阻塞当前线程,直到被调用的线程执行完毕。调用 join() 后,线程对象不再关联任何线程,即该线程对象不再可连接(joinable() 返回 false)
cpp
#include <iostream>
#include <thread>
void func() {
std::cout << "Thread is working..." << std::endl;
}
int main() {
std::thread t(func);
if (t.joinable()) {
t.join(); //主线程等待t完成
std::cout << "Thread has finished." << std::endl;
}
return 0;
}
2. detach()
detach() 方法将线程分离,使其在后台独立运行,当前线程不再对其进行控制。分离后的线程在执行完毕后会自动释放资源。一旦调用 detach(),线程对象也不再可连接。
cpp
#include <iostream>
#include <thread>
#include <chrono>
void func() {
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Detached thread has finished." << std::endl;
}
int main() {
std::thread t(func);
t.detach(); //主线程t分离
std::cout << "Main thread continues..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
注意:使用 detach() 时要确保线程访问的资源在其执行期间不会被销毁,否则会导致未定义行为
3.joinable()
-
作用:
joinable()
用于检查线程对象是否处于可结合状态 (即是否可以调用join()
或detach()
)。其返回值为布尔类型:true
:线程已启动且未调用过join()
或detach()
,仍持有底层线程资源。false
:线程不可结合,可能因以下原因:- 默认构造(未关联任何线程)。
- 已被移动(资源所有权转移至其他对象)
- 已调用
join()
或detach()
(资源已释放或分离)
-
使用场景:
-
安全调用
join()
或detach()
在调用join()
或detach()
前检查joinable()
,避免重复操作导致的未定义行为; -
异常处理与资源管理 若线程未正确处理,析构时可能导致程序终止(
std::terminate()
)。通过joinable()
确保析构前正确处理线程 -
移动语义与线程所有权转移 当线程对象被移动后,原对象变为不可结合
-
4.线程的移动语义
std::thread
类支持移动语义,但不支持复制语义。这意味着可以将一个线程的所有权从一个 std::thread
对象转移到另一个对象。
cpp
#include <iostream>
#include <thread>
void func() {
std::cout << "Thread is working..." << std::endl;
}
int main() {
std::thread t1(func);
std::thread t2 = std::move(t1); // 转移线程所有权
if (t2.joinable()) {
t2.join();
}
return 0;
}
在上述代码中,std::move(t1)
将线程的所有权从 t1
转移到 t2
,转移后 t1
不再关联任何线程。
5.RAII(Resource Acquisition Is Initialization,资源获取即初始化)管理线程
RAII 的核心思想是将资源的获取和释放与对象的生命周期绑定。当对象被创建时,资源被获取;当对象离开其作用域时,析构函数被自动调用,资源被释放。在多线程编程中,我们可以创建一个自定义的类(如ThreadGuard
),在其构造函数中接收一个 std::thread
对象,在析构函数中确保线程被正确处理(如调用 join()
)
cpp
#include <iostream>
#include <thread>
// 使用 RAII 管理线程的类
class ThreadGuard {
public:
// 构造函数,接收一个 std::thread 对象的引用
explicit ThreadGuard(std::thread& t) : thread_(t) {}
// 析构函数,确保线程在对象销毁时被正确处理
~ThreadGuard() {
if (thread_.joinable()) {
thread_.join();
}
}
// 禁用拷贝构造函数和拷贝赋值运算符,防止线程对象被意外复制
ThreadGuard(const ThreadGuard&) = delete;
ThreadGuard& operator=(const ThreadGuard&) = delete;
private:
std::thread& thread_; // 引用存储传入的线程对象
};
// 线程执行的函数
void threadFunction() {
std::cout << "Thread is running..." << std::endl;
}
int main() {
std::thread t(threadFunction); // 创建一个新线程
ThreadGuard guard(t); // 使用 ThreadGuard 管理线程
// 主线程继续执行其他任务
std::cout << "Main thread is doing other work..." << std::endl;
// 当 guard 对象离开作用域时,析构函数会自动调用,确保线程被正确处理
return 0;
}
三、线程同步与数据安全
1. 互斥量(Mutex)
- std::lock_guard
std::lock_guard
是一种 RAII 风格的锁,它在构造时自动加锁,在析构时自动解锁,大大简化了锁的管理。例如:
cpp
std::mutex mtx;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
shared_data++;
}
//std::lock_guard<std::mutex> lock(mtx)在进入safe_increment函数时自动对mtx互斥量加锁,当函数执行完毕,lock对象析构时自动解锁,确保了shared_data++操作的线程安全性。
- std::unique_lock
std::unique_lock
相较于std::lock_guard
更加灵活,它支持手动控制锁的加锁和解锁,尤其适用于与条件变量配合使用的场景。例如:
cpp
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
lock.lock(); // 手动加锁
//这里,std::unique_lock<std::mutex> lock(mtx, std::defer_lock)创建了一个unique_lock对象,但并不立即加锁(std::defer_lock参数指定了延迟加锁),随后通过lock.lock()手动对互斥量mtx加锁。
2. 条件变量(Condition Variable)
条件变量通常与互斥锁(如 std::mutex)一起使用。当一个线程需要等待某个条件成立时,它会先获取互斥锁,检查条件是否满足。如果条件不满足,线程会释放互斥锁并进入等待状态;当其他线程修改了共享状态并使条件满足时,它可以通过条件变量通知等待的线程。等待的线程在收到通知后会重新获取互斥锁并继续执行。
1. 头文件和类型
要使用条件变量,需要包含 <condition_variable>
头文件。C++ 标准库提供了两种条件变量类型:
std::condition_variable
:只能与std::unique_lock<std::mutex>
一起使用。std::condition_variable_any
:可以与任何满足锁类型要求的锁一起使用,但通常会有一些额外的开销。
2. 常用成员函数
- wait()
wait() 函数用于让线程等待条件变量的通知。它有两种重载形式:
void wait(std::unique_lock<std::mutex>& lock)
:线程会释放锁并进入等待状态,直到收到通知。收到通知后,线程会重新获取锁。
template< class Predicate > void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
:线程会先检查 pred 条件是否为 true,如果为 false,则释放锁并进入等待状态。收到通知后,线程会重新获取锁并再次检查 pred 条件,直到条件为 true 才会继续执行。 notify_one()
: 函数用于通知一个正在等待该条件变量的线程。如果有多个线程在等待,只会选择其中一个线程进行通知。notify_all()
: 函数用于通知所有正在等待该条件变量的线程。
3.案例
下面是一个使用条件变量实现生产者 - 消费者模型的案例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
bool isProducing = true;
// 生产者线程函数
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟生产时间
std::unique_lock<std::mutex> lock(mtx);
dataQueue.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one(); // 通知消费者有新数据
}
{
std::unique_lock<std::mutex> lock(mtx);
isProducing = false;
}
cv.notify_all(); // 通知所有消费者生产结束
}
// 消费者线程函数
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// 等待新数据或生产结束的通知
cv.wait(lock, [] { return!dataQueue.empty() ||!isProducing; });
if (!dataQueue.empty()) {
int value = dataQueue.front();
dataQueue.pop();
std::cout << "Consumed: " << value << std::endl;
} else if (!isProducing) {
break; // 生产结束且队列为空,退出循环
}
}
}
int main() {
std::thread producerThread(producer);
std::thread consumerThread(consumer);
producerThread.join();
consumerThread.join();
return 0;
}