欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 旅游 > 探索std::thread,让 C++ 程序 “火力全开”

探索std::thread,让 C++ 程序 “火力全开”

2025/3/9 19:50:14 来源:https://blog.csdn.net/2301_79965256/article/details/146115853  浏览:    关键词:探索std::thread,让 C++ 程序 “火力全开”

在 C++ 编程领域,多线程编程是提升程序性能和实现高效并发的关键手段。它允许程序同时执行多个任务,充分利用多核处理器的优势,在诸如游戏开发、服务器端编程、数据分析等诸多场景中发挥着重要作用。接下来,让我们一同学习线程的std::thread的相关知识。

一、线程的创建与启动

  • 函数指针作为线程入口
    通过 std::thread 构造函数传递函数指针或可调用对象,在传递参数时,默认是按值传递的。若需要传递引用类型的参数,则必须使用std::ref或std::cref
#include <thread>
void func(int x) { /* ... */ }
int main() {int num = 42;std::thread t(func, std::ref(num));  // 按引用传递t.join();
}
  • 类成员函数作为线程入口
    需要传递成员函数的指针、类对象的指针以及成员函数所需的参数
#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表达式作为线程入口
#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(),对象会被复制到线程存储空间
#include<iostream>
#include<thread>
struct Task{operator ()(){std::cout<<"Task executed"<<endl;}
}int main(){Task task;std::thread t(task);     //调用task的operatort.join;return  0}

二、线程生命周期管理

线程在 std::thread 对象构造时立即启动,而非调用 join() 时才开始执行

1. join()

join() 方法会阻塞当前线程,直到被调用的线程执行完毕。调用 join() 后,线程对象不再关联任何线程,即该线程对象不再可连接(joinable() 返回 false)

#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(),线程对象也不再可连接。

#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()

  1. 作用:joinable() 用于检查线程对象是否处于可结合状态(即是否可以调用 join()detach())。其返回值为布尔类型:

    • true:线程已启动且未调用过 join()detach(),仍持有底层线程资源。
    • false:线程不可结合,可能因以下原因:
      1. 默认构造(未关联任何线程)。
      2. 已被移动(资源所有权转移至其他对象)
      3. 已调用 join()detach()(资源已释放或分离)
  2. 使用场景:

    1. 安全调用 join()detach() 在调用 join()detach() 前检查 joinable(),避免重复操作导致的未定义行为;

    2. 异常处理与资源管理 若线程未正确处理,析构时可能导致程序终止(std::terminate())。通过 joinable() 确保析构前正确处理线程

    3. 移动语义与线程所有权转移 当线程对象被移动后,原对象变为不可结合

4.线程的移动语义

std::thread 类支持移动语义,但不支持复制语义。这意味着可以将一个线程的所有权从一个 std::thread 对象转移到另一个对象。

#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()

#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 风格的锁,它在构造时自动加锁,在析构时自动解锁,大大简化了锁的管理。例如:
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更加灵活,它支持手动控制锁的加锁和解锁,尤其适用于与条件变量配合使用的场景。例如:
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.案例
下面是一个使用条件变量实现生产者 - 消费者模型的案例:

#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;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词