c++ 多线程知识汇总

一、std::thread

std::thread 是 C++11 引入的标准库中的线程类,用于创建和管理线程

1. 带参数的构造函数

template <class F, class... Args>
std::thread::thread(F&& f, Args&&... args);F&& f:线程要执行的函数;Args&&... args:可变参数,用于将参数转发到函数 f

2. 方法

void join(); 等待线程结束。 注:线程必须是可 join 的(即线程正在运行且未被分离)
void detach(); 分离线程,使其独立运行。 注:线程必须正在运行且未被分离
void swap(std::thread& other); 交换两个线程对象
std::thread::id get_id() const; 获取线程的唯一标识符
bool joinable() const; 检查线程是否可以被 join
std::thread& operator=(std::thread&& other); 移动赋值运算符,用于将一个线程对象移动到另一个线程对象

3. 简单例子

3.1 普通函数作为线程执行函数

#include <iostream>
#include <thread>void threadFunction(int id) {std::cout << "Thread " << id << " is running" << std::endl;
}int main() {std::thread t(threadFunction, 1); // 创建线程并传递参数if (t.joinable()) {t.join(); // 等待线程结束}// t.dedetach(); // 分离线程 ,分离的线程会在后台独立运行,即使创建它的线程已经结束return 0;
}注意 : 如果需要传引用时,临时变量不行,可以用std::ref(变量名)把他转成引用类型传递指针或引用时不能用临时变量。离开作用域就销毁了,线程函数里就取不到了临时的类对象也不行,都会导致错误,总之,要控制好声明周期,保证线程执行时能正确使用。

3.2 类的成员函数作为线程执行函数

#include <iostream>
#include <thread>
#include <functional>class MyClass {
public:void memberFunction(int id) {std::cout << "Thread " << id << " is running in MyClass" << std::endl;}
};int main() {MyClass obj;std::thread t(std::bind(&MyClass::memberFunction, &obj, 1)); // 使用 std::bind 绑定成员函数和对象t.join();return 0;
}

4. 互斥量

        线程安全:线程安全的代码能够保证在并发执行时,程序的行为符合预期,且不会因线程之间的竞争条件(Race Condition)而产生错误。下面代码,线程不安全,输出是不固定的。

int shared_data = 0;void myThread()
{for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}

        互斥量是一种同步原语,用于保护共享资源,防止多个线程同时访问。互斥量确保同一时间只有一个线程可以持有互斥量,从而保证对共享资源的互斥访问。确保线程安全。

C++11 引入了 <mutex> 头文件,其中定义了多种类型的互斥量。

4.1 std::mutex

   std::mutex 是最基本且最常用的互斥量类型,它不支持递归加锁。

int shared_data = 0;
std::mutex mtx; // 定义互斥量void myThread()
{mtx.lock();for (int i=0; i < 10000; ++ i){shared_data++;}mtx.unlock();
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}此时无论运行多少次,输出都是相同的。

  4.2 std::lock_guard

        为了简化互斥量的使用 C++ 提供了 std::lock_guard 它是一个 RAII 风格的工具,用于自动管理互斥量的加锁和解锁。lock_guard 对象不能复制或移动,因此他只能在局部作用域中。

#include <iostream>
#include <thread>
#include <mutex>int shared_data = 0;
std::mutex mtx; // 定义互斥量void myThread()
{std::lock_guard<std::mutex> lock(mtx); // 自动加锁(析构中自动解锁)for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}

4.3 std::unique_lock

4.3.1 概念

        std::unique_lock 是 C++ 标准库中用于管理互斥锁的一种智能锁类,它提供了比 std::lock_guard 更灵活的锁管理功能。std::unique_lock 不仅可以自动管理锁的获取和释放,还可以在需要时手动控制锁的行为,例如延迟锁的获取、尝试锁的获取以及在特定条件下释放锁等。

 4.3.2 特点

        std::unique_lock允许在构造时延迟锁的获取,或者在运行时尝试获取锁,而 std::lock_guard 只能在构造时立即获取锁

4.3.3 方法

// 绑定到互斥量的构造函数
std::unique_lock(std::mutex& m):立即获取互斥量 m。
std::unique_lock(std::mutex& m, std::defer_lock_t):延迟获取互斥量 m,需要后续手动调用 lock()。
std::unique_lock(std::mutex& m, std::try_to_lock_t):尝试立即获取互斥量 m,如果失败则不会阻塞。
std::unique_lock(std::mutex& m, std::adopt_lock_t):假设互斥量 m 已经被当前线程锁定,std::unique_lock 只是接管锁的所有权。成员函数void lock(); 尝试获取互斥量。如果互斥量已被其他线程锁定,则当前线程会阻塞,直到互斥量可用。
bool try_lock(); 尝试立即获取互斥量。如果成功获取,则返回 true;如果失败,则返回 false,且不会阻塞。template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
尝试在指定的相对时间 rel_time 内获取互斥量。template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time);
尝试在指定的绝对时间 abs_time 之前获取互斥量void unlock();
释放互斥量。如果互斥量未被当前线程锁定,则行为未定义。
bool owns_lock() const;
返回 true 如果当前 std::unique_lock 对象拥有互斥量的所有权,否则返回 false。
std::mutex* mutex() const;
返回绑定的互斥量的指针,如果没有绑定互斥量,则返回 nullptr。
void swap(std::unique_lock& other);
交换当前对象和另一个 std::unique_lock 对象的状态
std::mutex* release();
返回指向它所管理的互斥量的指针,并释放所有权。
4.3.4 代码示例

① 立即获取锁,与lock_guard相同,立即获取锁

int shared_data = 0;
std::mutex mtx;void myThread()
{std::unique_lock<std::mutex> u_lock(mtx);for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}

② 尝试立即加锁

std::mutex mtx;
void foo() {std::unique_lock<std::mutex> lock(mtx, std::try_to_lock); // 尝试立即加锁if (lock.owns_lock()) {// 成功获取锁// 临界区代码} else {// 未能获取锁}
}

③ 尝试在指定的时间段内获取互斥锁

        需要结合可超时互斥量,后面介绍。

int shared_data = 0;
std::timed_mutex mtx; // 可超时互斥量void myThread()
{// 构造函数传入defer_lock 表示构造但是不加锁,需要手动加锁,结合延迟使用std::unique_lock<std::timed_mutex> u_lock(mtx, std::defer_lock);//尝试加锁,只等待1秒,1秒内没获取到资源直接返回了if (u_lock.try_lock_for(std::chrono::seconds(1))){std::this_thread::sleep_for(std::chrono::seconds(1));// 睡三秒for (int i=0; i < 10000; ++ i){shared_data++;}}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}
// 输出1000 或两千

⑤ try_lock_until 等待获取资源,超过了指定时间,就不阻塞了,直接返回。(不常用)

4.4 std::timed_mutex

4.4.1 概念

       std::timed_mutex 是 C++ 标准库中 <mutex> 头文件定义的一种互斥量类型,它是一种可超时的互斥量,提供了在尝试锁定时支持超时的功能。它继承自 std::mutex,和普通互斥量一样,std::timed_mutex 保证同一时刻只有一个线程可以持有锁,用于保护共享资源。

4.4.2 主要成员函数
1. lock()
阻塞当前线程,直到获取锁为止。
如果其他线程已经持有锁,则当前线程会阻塞,直到锁被释放。
2. try_lock()
尝试立即获取锁。
如果锁当前可用,则获取锁并返回 true;如果锁已被占用,则返回 false,不会阻塞。
3. try_lock_for(std::chrono::duration)
尝试在指定的时间间隔内获取锁。
如果在超时期间内获取到锁,则返回 true;否则返回 false。
4. try_lock_until(std::chrono::time_point)
尝试在指定的时间点之前获取锁。
如果在指定时间点之前获取到锁,则返回 true;否则返回 false。
5. unlock()
释放锁,允许其他线程获取锁。

4.5 互斥量死锁

4.5.1 死锁概念

        死锁指的是两个或多个线程因为相互等待对方持有的资源而无法继续执行的状态。

死锁的四个必要条件
根据死锁的理论,发生死锁需要同时满足以下四个必要条件:
互斥条件(Mutual Exclusion):
资源不能被共享,只能被一个线程独占使用。
例如,互斥量(std::mutex)确保同一时间只有一个线程可以访问资源。
请求和保持条件(Hold and Wait):
一个线程已经持有了某个资源,但又请求其他资源,而这些资源已经被其他线程持有。
例如,线程 A 持有资源 X,但请求资源 Y;线程 B 持有资源 Y,但请求资源 X。
不可剥夺条件(No Preemption):
已经分配给线程的资源不能被强制剥夺,只能由持有资源的线程主动释放。
例如,互斥量一旦被线程加锁,其他线程无法强制解锁。
循环等待条件(Circular Wait):
存在一个线程等待资源的循环链,即线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,线程 C 等待线程 A 持有的资源。
例如,线程 A 等待资源 Y,线程 B 等待资源 X,而资源 X 和 Y 分别被线程 B 和线程 A 持有。
4.5.2 死锁的示例
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;void threadFunction1() {std::cout << "Thread 1: Trying to lock mtx1" << std::endl;mtx1.lock();std::cout << "Thread 1: Locked mtx1, trying to lock mtx2" << std::endl;mtx2.lock();std::cout << "Thread 1: Locked mtx2" << std::endl;// 业务逻辑std::this_thread::sleep_for(std::chrono::seconds(1));mtx2.unlock();mtx1.unlock();
}void threadFunction2() {std::cout << "Thread 2: Trying to lock mtx2" << std::endl;mtx2.lock();std::cout << "Thread 2: Locked mtx2, trying to lock mtx1" << std::endl;mtx1.lock();std::cout << "Thread 2: Locked mtx1" << std::endl;// 业务逻辑std::this_thread::sleep_for(std::chrono::seconds(1));mtx1.unlock();mtx2.unlock();
}int main() {std::thread t1(threadFunction1);std::thread t2(threadFunction2);t1.join();t2.join();return 0;
}

4.5.3 避免死锁的方法

① 固定加锁顺序

② 使用 std::try_lock 或超时机制

5. std::call_once(只能在线程里使用)

5.1 概念

        std::call_once 是 C++11 引入的一个非常有用的工具,用于确保某个函数或操作只被调用一次,即使在多线程环境中也能保证线程安全。它常用于实现单例模式、初始化全局资源等场景。

5.2 单例模式,多线程情况下,初始化可能被执行多次,违背了单例模式,可以把初始化操作封到一个成员函数里,然后把这个函数加上 call_once 。避免被多次初始化。

6. std::condition_variable 条件变量

6.1 概念

        std::condition_variable 是 C++ 标准库中用于线程同步的工具之一,它允许线程在某些条件尚未满足时进入休眠状态,并在条件满足时被唤醒。 通常与互斥锁(std::mutexstd::timed_mutex)配合使用,以实现线程间的协调和同步。

6.2 生产者与消费者模型

        生产者与消费者之间存在一个任务队列,生产者负责产生任务,存入任务队列,并通知消费者需要干活了,消费者在任务队列为空时就会等待。直到有数据了并接到了生产者的通知(唤醒),被唤醒后则从队列中取出任务执行。 

6.3 代码示例

wait的实现原理:函数接收一个锁定的对象,和一个谓词。当调用时,先释放锁,当前线程进入休眠,等待被唤醒,被唤醒时尝试获取锁,判断谓词是否为真,都满足了该线程继续执行,不满足则继续调用wait睡眠(谓词就是可以避免虚假唤醒)。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
#include <atomic>// 共享资源
std::queue<int> data_queue; // 数据队列
std::mutex mtx;             // 互斥锁
std::condition_variable cv; // 条件变量
std::atomic<bool> done(false); // 标志位,表示生产者是否完成// 生产者函数
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);data_queue.push(i); // 生产数据并放入队列std::cout << "Produced: " << i << std::endl;cv.notify_one(); // 唤醒一个消费者std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间}done = true; // 生产者完成cv.notify_all(); // 唤醒所有消费者
}// 消费者函数
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return !data_queue.empty() || done.load(); }); // 等待队列非空或生产者完成if (done.load() && data_queue.empty()) {break; // 如果生产者完成且队列为空,退出循环}int data = data_queue.front(); // 从队列中取出数据data_queue.pop();std::cout << "Consumed-------: " << data << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费时间}
}int main() {std::thread producer_thread(producer); // 创建生产者线程std::thread consumer_thread(consumer); // 创建消费者线程producer_thread.join(); // 等待生产者线程完成consumer_thread.join(); // 等待消费者线程完成return 0;
}

7. 线程池

7.1 概述

        线程池是一种用来管理和复用线程的技术。它通过提前创建一定数量的线程(工作线程),这些线程处于等待状态,等待从任务队列中获取任务并执行。任务执行完毕后,线程返回线程池等待处理下一个任务。线程池的核心思想是减少线程创建和销毁的开销,提高程序的性能和资源利用率。

优势:
①减少线程创建和销毁的开销:
线程的创建和销毁是一个相对耗时的操作,线程池通过复用线程避免了频繁创建和销毁线程的开销。
②提高程序的并发性能:
线程池可以合理控制线程的数量,避免因线程过多导致系统资源耗尽,从而提高程序的并发性能。
③简化线程管理:
线程池提供了一种统一的线程管理机制,使得线程的创建、调度和销毁更加方便和高效

7.2 示例代码

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <vector>
#include <memory>
#include <future>class ThreadPool {
public:ThreadPool(size_t num_threads);~ThreadPool();template <class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};ThreadPool::ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if (this->stop && this->tasks.empty()) {return;}task = std::move(this->tasks.front());this->tasks.pop();}task();}});}
}ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers) {worker.join();}
}template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;
}// 示例用法
int main() {ThreadPool pool(4);auto result1 = pool.enqueue([](int answer) { return answer; }, 42);auto result2 = pool.enqueue([](int a, int b) { return a + b; }, 5, 7);std::cout << "Result 1: " << result1.get() << std::endl;std::cout << "Result 2: " << result2.get() << std::endl;return 0;
}

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>class TheradPool
{
public:TheradPool(int thread_num) : stop_(false){for (int i=0; i < thread_num; ++i){threads_.emplace_back([this](){while(1){std::unique_lock<std::mutex> uptr_lock(mtx_);condition_.wait(uptr_lock, [this](){return !tasks_.empty() || stop_;});if (stop_ && tasks_.empty()){return;}std::function<void()> task(std::move(tasks_.front()));tasks_.pop();uptr_lock.unlock();task();}});}}// F 是函数参数类型,表示可调用对象的类型(如函数指针、lambda 表达式、函数对象等)
// Args... 是可变参数模板,表示可调用对象的参数类型列表。... 表示参数包,可以接受任意数量和类型的参数template <typename F, typename... Args>void enqueue(F&& f, Args&&... args){// F&& f 是一个右值引用,表示可调用对象。使用右值引用是为了支持完美转发,//即可以将 f 作为左值或右值传递给后续的调用std::function<void()> task = std::bind(std::forward<F> (f), std::forward<Args>(args)...);{std::unique_lock<std::mutex> uptr_lock(mtx_);tasks_.emplace(std::move(task));}condition_.notify_one();}~TheradPool(){// 控制锁的作用域,设置完stop_立即释放锁,确保其他线程能拿到锁,避免死锁{std::unique_lock<std::mutex> uptr_lock(mtx_);stop_ = true;}condition_.notify_all();for (auto &thread : threads_){thread.join();}}private:std::vector<std::thread> threads_;std::queue<std::function<void()>> tasks_;std::mutex mtx_;std::condition_variable condition_;bool stop_;};

7.3 项目代码

#ifndef __COMMON_UTILS_THREAD_POOL_HPP__
#define __COMMON_UTILS_THREAD_POOL_HPP__#include <vector>
#include <thread>
#include <future>
#include <functional>
#include <utility>
#include <mutex>#include "sp.hpp"
#include "BlockQueue.hpp"
#include "Threads.hpp"namespace utils {class ThreadPool {public:ThreadPool(int thread_num, std::string name): shutdown_(false), name_(name),threads_(std::vector<sp<std::thread>>(thread_num, nullptr)),task_queue_(BlockQueue<std::function<void()>>(1000)){}public:void Init(){for (int i = 0; i < threads_.size(); i++){// 每一个线程都从任务队列取任务threads_[i] = std::make_shared<std::thread>([this,i]{common::utils::SetThreadName(std::string(name_ + std::to_string(i)).c_str());for(;;){std::function<void()> task;bool success = false;{std::unique_lock<std::mutex> lk(this->condition_mutex_);this->condition_.wait(lk, [this]{return this->shutdown_ || !this->task_queue_.IsEmpty();});if (this->shutdown_ && this->task_queue_.IsEmpty()) {return;}success = this->task_queue_.Pop(task);}if (success){task();}}});            }}void Shutdown(){{std::unique_lock<std::mutex> lock(condition_mutex_);shutdown_ = true;}condition_.notify_all();for (int i = 0; i < threads_.size(); ++i){if (threads_[i]->joinable()){threads_[i]->join();threads_[i] = nullptr;}}}template<class F,class... Args>auto Submit(F &&f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>{using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(condition_mutex_);if (shutdown_){throw std::runtime_error("submit on stopped threadpool");}task_queue_.Push([task]() { (*task)(); });}condition_.notify_one();return res;}private:private:ThreadPool(const ThreadPool&) = delete;ThreadPool(ThreadPool&&) = delete;ThreadPool& operator=(const ThreadPool&) = delete;ThreadPool& operator=(ThreadPool&&) = delete;private:bool shutdown_;std::vector<sp<std::thread>> threads_;std::condition_variable condition_;std::mutex condition_mutex_;BlockQueue<std::function<void()>> task_queue_;std::string name_;
};}#endif

8. 异步并发 (async , future)

8.1 async

        std::async 是 C++11 引入的一个用于异步任务执行的函数模板,它提供了一种简单的方式来启动一个任务,并通过 std::future 获取任务的返回值。

        std::async:用于启动一个异步任务,返回一个 std::future 对象,通过该对象可以获取任务的返回值或处理任务中的异常;std::future:表示异步操作的结果,可以用来获取异步任务的返回值或检查任务是否完成。

std::future<R> std::async(std::launch policy, F&& f, Args&&... args);
返回值:std::async 返回一个 std::future<R> 对象,其中 R 是函数 f 的返回类型。std::future 是一个用于获取异步操作结果的对象。
参数:
std::launch policy:启动策略,用于指定任务的执行方式。
F&& f:可调用对象(如函数指针、lambda 表达式、函数对象等)。
Args&&... args:可调用对象的参数列表,支持可变参数。
#include <iostream>
#include <future>
#include <thread>
#include <chrono>int add(int a, int b) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作return a + b;
}int main() {std::future<int> result = std::async(add, 5, 3); // 使用默认策略std::cout << "Waiting for the result..." << std::endl;int sum = result.get(); // 阻塞等待任务完成std::cout << "Result: " << sum << std::endl;return 0;
}

8.2 std::packaged_task

       std::packaged_task 是 C++11 引入的一个模板类,用于包装可调用对象(如函数、lambda 表达式、函数对象等),使其能够异步执行,并通过 std::future 获取任务的执行结果。

#include <iostream>
#include <future>
#include <thread>// 一个简单的函数,计算整数的平方
int square(int x) {return x * x;
}int main() {// 创建一个 std::packaged_task,包装 square 函数std::packaged_task<int(int)> task(square);// 获取与 packaged_task 关联的 std::future 对象std::future<int> result = task.get_future();// 将任务交给一个线程异步执行std::thread t(std::move(task), 10); // 传递参数 10 给 square 函数// 等待任务完成并获取结果int value = result.get();std::cout << "The square of 10 is " << value << std::endl;t.join(); // 等待线程结束return 0;
}

8.3 std::promise

        std::promise 是 C++11 引入的一个模板类,用于存储异步操作的结果,并通过 std::future 将结果传递给其他线程。std::promisestd::future 通常一起使用,用于实现线程间的异步通信和结果传递。

主要功能
存储异步操作的结果:std::promise 可以存储一个值或异常,作为异步操作的结果。
与 std::future 关联:通过 std::promise 的 get_future() 方法,可以获取一个与之关联的 std::future 对象。
设置结果:通过 std::promise 的 set_value() 方法设置操作的返回值,或通过 set_exception() 方法设置异常。
线程间通信:std::promise 和 std::future 提供了一种机制,允许在不同线程之间安全地传递异步操作的结果。
#include <iostream>
#include <future>
#include <thread>
#include <chrono>void worker(std::promise<int> promise) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作promise.set_value(42); // 设置异步操作的结果
}int main() {std::promise<int> promise; // std::promise 对象,用于存储异步操作的结果std::future<int> future = promise.get_future(); // 获取与 promise 关联的 future 对象std::thread t(worker, std::move(promise)); // 启动一个线程,将 promise 传递给 worker 函数std::cout << "Waiting for the result..." << std::endl;int result = future.get(); // 阻塞等待结果std::cout << "Result: " << result << std::endl;t.join(); // 等待线程结束return 0;
}

8.4 std::async 和 std::thread 有什么区别?

std::asyncstd::thread 都是 C++ 标准库中用于实现并发和多线程的工具,但它们在功能、使用方式和用途上存在一些重要区别。

std::thread 它直接创建一个线程,并且完全由用户控制线程的生命周期。适合需要手动管理线程的场景,例如需要精确控制线程的创建、启动、销毁以及线程间的同步;

std::async 它用于启动一个异步任务,并且返回一个 std::future 对象,用于获取异步任务的结果。适合用于启动一个异步任务,并且希望在任务完成后获取结果的场景。它隐藏了线程管理的细节,更侧重于任务的异步执行。

std::thread 没有返回值 std::async 返回一个 std::future 对象 通过 std::futureget()wait() 方法可以获取异步任务的返回值。

  • std::thread

    • 性能:创建和销毁线程的开销较大,特别是频繁创建和销毁线程时。

    • 资源管理:需要手动管理线程资源,容易出现资源泄漏或线程死锁等问题。

  • std::async

    • 性能:内部可能使用线程池,减少了线程的创建和销毁开销。

    • 资源管理:由标准库管理,减少了资源管理的复杂性。


9. 原子操作 atomic

std::atomic 是 C++11 标准库中引入的一个模板类,用于实现原子操作。它确保对变量的读取、修改和存储操作是不可分割的(即原子的),即使在多线程环境下也不会出现中间状态被其他线程干扰的情况.

常用类型
C++ 标准库提供了多种原子类型,包括:
std::atomic<bool>:原子布尔类型
std::atomic<int>:原子整数类型
std::atomic<T*>:原子指针类型
std::atomic<std::shared_ptr<T>>:原子共享指针类型
#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);void increment_counter() {for (int i = 0; i < 100000; ++i) {counter.fetch_add(1, std::memory_order_relaxed); // 原子加1}
}int main() {std::thread t1(increment_counter);std::thread t2(increment_counter);t1.join();t2.join();std::cout << "Counter value: " << counter.load() << std::endl; // 安全地读取值return 0;
}

10. 线程间通信的方式

除了 前面提到的std::promisestd::future,C++ 提供了多种线程间通信机制,每种机制都有其适用场景和特点。以下是一些常见的替代方案:

1. 共享内存 + 同步原语

共享内存是最直接的线程间通信方式,通过全局变量、静态变量或堆上分配的对象实现。为了防止数据竞争,需要使用同步原语(如互斥锁 std::mutex)来保护对共享变量的访问。

2. 消息队列

消息队列允许线程通过发送和接收消息来交换信息,而不是直接操作共享内存。可以使用 std::queue 和条件变量 std::condition_variable 实现一个简单的线程安全消息队列。

3. 条件变量

条件变量是一种同步原语,允许线程等待某个条件成立,然后被其他线程唤醒。它通常与互斥锁 std::mutex 配合使用,适用于复杂的同步逻辑。

4. 信号量

信号量是一种计数器,用于控制对共享资源的访问。线程可以通过 acquire() 请求资源,通过 release() 释放资源。C++20 引入了 std::counting_semaphore,可以方便地实现线程间的同步。

5. 读写锁

读写锁(std::shared_mutex)允许多个线程同时读取共享资源,但写操作需要独占访问。它适用于读多写少的场景。

6. 事件

事件是一种简单的同步机制,线程可以等待某个事件的发生,而其他线程可以触发事件。可以通过条件变量实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/18897.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5接入支付宝手机网站支付并实现

小程序文档 - 支付宝文档中心 1.登录 支付宝开放平台 创建 网页/移动应用 2.填写创建应用信息 3.配置开发设置 4.网页/移动应用&#xff1a;需要手动上线。提交审核后&#xff0c;预计 1 个工作日的审核时间。详细步骤可点击查看 上线应用 。应用上线后&#xff0c;还需要完成…

字节二面:DNS是什么?是什么原理?

写在前面 最近有个同学后台私信让我出一个DNS的工作原理&#xff0c;面试的时候居然问到了&#xff0c;所以就简单聊聊DNS的工作原理吧&#xff01; 1. DNS 的核心作用 DNS&#xff08;域名系统&#xff0c;Domain Name System&#xff09;是互联网中用于将人类可读的域名转…

【Unity3D】Jenkins Pipeline流水线自动构建Apk

目录 一、准备阶段 二、创建Pipeline流水线项目 三、注意事项 四、扩展 1、Pipeline添加SVN更新项目Stage阶段 一、准备阶段 1、安装tomcat 10.0.5 Index of apache-local/tomcat/tomcat-10 2、安装jdk 17 Java Archive Downloads - Java SE 17.0.13 and later 3、…

【数据结构】(9) 优先级队列(堆)

一、优先级队列 优先级队列不同于队列&#xff0c;队列是先进先出&#xff0c;优先级队列是优先级最高的先出。一般有两种操作&#xff1a;返回最高优先级对象&#xff0c;添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构&#xff0c;是一棵完全二叉树&#xff0c…

2025.2.15

web [HNCTF 2022 Week1]Interesting_include&#xff1a; 直接打开 PHP代码片段包含两部分&#xff1a;一个主脚本和一个潜在的被包含文件。主脚本负责处理GET请求&#xff0c;特别是filter参数&#xff0c;而被包含文件&#xff08;假设为./flag.php&#xff09;似乎包含了我…

CentOS 7.8 安装MongoDB 7教程

文章目录 CentOS 7.8 安装MongoDB 7教程一、准备工作1. 系统更新2. 权限 二、添加MongoDB软件源1. 创建MongoDB的yum源文件2. 添加以下内容3. 保存并退出编辑器 三、安装MongoDB1. 更新yum缓存2. 安装MongoDB 四、启动MongoDB服务1. 启动MongoDB2. 设置MongoDB开机自启动 五、配…

ElasticSearch基础和使用

ElasticSearch基础 1 初识ES相关组件 &#xff08;1&#xff09;Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack&#xff08;ELK&#xff09; 广泛应…

[C++]多态详解

目录 一、多态的概念 二、静态的多态 三、动态的多态 3.1多态的定义 3.2虚函数 四、虚函数的重写&#xff08;覆盖&#xff09; 4.1虚函数 4.2三同 4.3两种特殊情况 &#xff08;1&#xff09;协变 &#xff08;2&#xff09;析构函数的重写 五、C11中的final和over…

【git-hub项目:YOLOs-CPP】本地实现01:项目构建

目录 写在前面 项目介绍 最新发布说明 Segmentation示例 功能特点 依赖项 安装 克隆代码仓库 配置 构建项目 写在前面 前面刚刚实现的系列文章: 【Windows/C++/yolo开发部署01】 【Windows/C++/yolo开发部署02】 【Windows/C++/yolo开发部署03】 【Windows/C++/yolo…

在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程

既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…

安装 Docker Desktop 修改默认安装目录到指定目录

Docker Desktop安装目录设置 Docker Desktop 默认安装位置 &#xff08;C:\Program Files\Docker\Docker) 是这个 &#xff0c;导致系统盘占用过大&#xff0c;大概2G ; 那么如何安装到其他磁盘呢&#xff1f; 根据docker desktop 官网 Docker Desktop install 我们可以看到&a…

DeepSeek官方发布R1模型推荐设置

今年以来&#xff0c;DeepSeek便在AI领域独占鳌头&#xff0c;热度一骑绝尘。其官方App更是创造了惊人纪录&#xff0c;成为史上最快突破3000万日活的应用&#xff0c;这一成绩无疑彰显了它在大众中的超高人气与强大吸引力。一时间&#xff0c;各大AI及云服务厂商纷纷投身其中&…

常见的IP地址分配方式有几种:深入剖析与适用场景‌

在数字互联的世界里&#xff0c;IP地址如同网络世界的“门牌号”&#xff0c;是设备间通信的基础。随着网络技术的飞速发展&#xff0c;IP地址的分配方式也日趋多样化&#xff0c;以适应不同规模、不同需求的网络环境。本文将深入探讨当前主流的几种IP地址分配方式&#xff0c;…

NLP 八股 DAY1:BERT

BERT全称&#xff1a;Pre-training of deep bidirectional transformers for language understanding&#xff0c;即深度双向Transformer。 模型训练时的两个任务是预测句⼦中被掩盖的词以及判断输⼊的两个句⼦是不是上下句。在预训练 好的BERT模型后⾯根据特定任务加上相应的⽹…

Flutter_学习记录_动画的简单了解

用AnimationController简单实现如下的效果图&#xff1a; 1. 只用AnimationController实现简单动画 1.1 完整代码案例 import package:flutter/material.dart;class AnimationDemo extends StatefulWidget {const AnimationDemo({super.key});overrideState<AnimationDe…

sql sqlserver的特殊函数COALESCE和PIVOT的用法分析

一、COALESCE是一个返回参数中第一个非NULL值的函数&#xff0c; 列如&#xff1a;COALESCE&#xff08;a,b,c,d,e&#xff09;;可以按照顺序取abcde&#xff0c;中的第一个非空数据&#xff0c;abcde可以是表达式 用case when 加ISNULL也可以实现&#xff0c;但是写法复杂了…

类与对象C++详解(上)

目录 1.类的定义 1.1 类定义格式 补充: struct与class的区别&#xff08;c语言与c&#xff09; 1.2 访问限定符 1.3 类域 2.实例化 3.对象大小 4.this指针 1.类的定义 1.1 类定义格式 class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&…

LabVIEW 天然气水合物电声联合探测

天然气水合物被认为是潜在的清洁能源&#xff0c;其储量丰富&#xff0c;预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性&#xff0c;天然气水合物的探测面临诸多挑战&#xff0c;涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…

Windows上安装Go并配置环境变量(图文步骤)

前言 1. 本文主要讲解的是在windows上安装Go语言的环境和配置环境变量&#xff1b; Go语言版本&#xff1a;1.23.2 Windows版本&#xff1a;win11&#xff08;win10通用&#xff09; 下载Go环境 下载go环境&#xff1a;Go下载官网链接(https://golang.google.cn/dl/) 等待…

神经网络常见激活函数 9-CELU函数

文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元&#xff1a;CELU&#xff08;Continuously Differentiable Exponential Linear Unit&#xff09;,是一种连续可导的激活函数&#xff0c;结合了 ELU 和 …