当前位置: 首页 > news >正文

C++11并发支持库

在这里插入图片描述

C++11并发支持库

  • 一、thread
    • (一)thread类
      • 1、构造函数
    • `std::thread` 就是 **“把函数扔给后台跑”** 的利器; 若多个线程同时改同一份数据,记得用 `std::atomic` 防止 race-condition。
      • 2、常用接口
      • 3、 完整可运行示例
    • (二)命名空间域std::this_thread
      • 1、四个成员函数
      • 2、用法用例
  • 二、mutex/lock_guard/unique_lock
    • (一)互斥量/RAII智能锁
    • (二)常用接口
    • (三)用法介绍
  • 三、时间命名空间chrono的一些成员
    • (一)两个重要的类
    • (二)具体用法
  • 四、atomic
    • (一)std::atomic 核心接口(store/load/exchange/CAS)
      • 1、接口介绍
      • 2、实例
        • store / load
        • exchange —— 实现自旋锁
        • compare_exchange_weak
        • compare_exchange_strong
        • compare_exchange_strong
    • (二)memory_order
      • 1、为什么需要 memory_order
      • 2、六种meory_order选项
        • `memory_order_acquire`和`memory_order_release`
        • `memory_order_seq_cst`
      • 实战建议
  • 五、conditon_variable
  • 六、异步操作常用接口
    • (一)std::async
    • (二)std::promise
    • (三)future / shared_future
    • (四)package_task

一、thread

(一)thread类

1、构造函数

std::thread 就是 “把函数扔给后台跑” 的利器; 若多个线程同时改同一份数据,记得用 std::atomic 防止 race-condition。

在这里插入图片描述
接口参考https://legacy.cplusplus.com/reference/thread/thread/


三种最常用构造方式

场景示范代码注意
① 全局函数std::thread(increase_global, 1000)直接传参即可
② 带引用参数std::thread(increase_reference, std::ref(foo), 1000)std::thread 的传参系统不认引用,必须用 std::ref 强行“传引用”。,否则拷贝
③ 类成员函数std::thread(&C::increase_member, std::ref(bar), 1000)第一个实参是对象引用

2、常用接口

接口原型作用使用时机注意事项
get_idstd::thread::id get_id() const noexcept获取线程唯一 ID调试、日志、区分线程空线程返回 std::thread::id()
joinablebool joinable() const noexcept判断线程是否可 join调用 join/detach 前检查默认构造、已 join/detach 都返回 false
joinvoid join()阻塞等待线程结束必须等待子线程完成只能调一次;线程空或已 detachstd::system_error
detachvoid detach()让线程后台运行,与主线程分离不关心线程何时结束一旦 detach 后无法再 join;线程对象置空

3、 完整可运行示例

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>/* ---------- ① 全局函数 ---------- */
std::atomic<int> global_counter{0};
void increase_global(int n) {for (int i = 0; i < n; ++i) ++global_counter;
}/* ---------- ② 引用参数 ---------- */
void increase_reference(std::atomic<int>& var, int n) {for (int i = 0; i < n; ++i) ++var;
}/* ---------- ③ 成员函数 ---------- */
struct C : std::atomic<int> {C() : std::atomic<int>(0) {}void increase_member(int n) {for (int i = 0; i < n; ++i) fetch_add(1);}
};int main() {std::vector<std::thread> threads;/* 1. 全局函数 */for (int i = 0; i < 10; ++i)threads.emplace_back(increase_global, 1000);/* 2. 引用参数 */std::atomic<int> foo{0};for (int i = 0; i < 10; ++i)threads.emplace_back(increase_reference, std::ref(foo), 1000);/* 3. 成员函数 */C bar;for (int i = 0; i < 10; ++i)threads.emplace_back(&C::increase_member, std::ref(bar), 1000);/* 同步 */for (auto& th : threads) th.join();std::cout << "global_counter = " << global_counter << '\n'<< "foo            = " << foo << '\n'<< "bar            = " << bar << '\n';return 0;
}

(二)命名空间域std::this_thread

1、四个成员函数

函数原型作用示例代码备注
get_idstd::thread::id get_id() const noexcept获取当前线程的唯一 IDstd::cout << std::this_thread::get_id();空线程返回默认构造的 id()
yieldvoid yield() noexcept主动让出当前线程剩余时间片std::this_thread::yield();提示调度器“我闲着”,不保证立刻切换
sleep_untiltemplate< class Clock, class Duration > void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time )阻塞到绝对时间点std::this_thread::sleep_until(tp);时间未到线程保持阻塞
sleep_fortemplate< class Rep, class Period > void sleep_for( const std::chrono::duration<Rep,Period>& rel_time )阻塞相对时长std::this_thread::sleep_for(500ms);最常用,单位可自动推导

2、用法用例

#include <iostream>
#include <thread>
#include <chrono>int main()
{using namespace std::chrono_literals;/* 1. get_id */std::cout << "main thread id = " << std::this_thread::get_id() << '\n';/* 2. yield */std::cout << "yield example (no output, just hint)\n";std::this_thread::yield();/* 3. sleep_for */std::cout << "sleep 500ms...\n";std::this_thread::sleep_for(500ms);/* 4. sleep_until */auto tp = std::chrono::steady_clock::now() + 1s;std::cout << "sleep until 1s later...\n";std::this_thread::sleep_until(tp);std::cout << "done\n";return 0;
}

二、mutex/lock_guard/unique_lock

(一)互斥量/RAII智能锁

类 / 接口作用常用构造常用成员特点
std::mutex原始互斥量std::mutex m;lock() / unlock() / try_lock()最底层,需手动解锁
std::lock_guard轻量 RAII 锁lock_guard<mutex> lg(m);无(不可拷贝不可移动)构造加锁,析构解锁,简单高效
std::unique_lock全能 RAII 锁unique_lock<mutex> ul(m);lock() / unlock() / try_lock() / try_lock_for() / release()可延迟锁、可反复锁、可条件变量配合

(二)常用接口

接口一句话
mutex::lock()阻塞直到拿到锁
mutex::try_lock()拿不到立即返回 false
mutex::unlock()释放锁
lock_guard构造即锁,析构即解,不能中途解
unique_lock想锁就锁,想解就解,还能给 condition_variable

(三)用法介绍

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex g_mtx;                      // 1. 原始互斥量
int g_count = 0;void worker(int id)
{/* 2. lock_guard:简单粗暴 */{std::lock_guard<std::mutex> lg(g_mtx);++g_count;std::cout << "thread " << id << " count=" << g_count << '\n';}                                  // 自动解锁/* 3. unique_lock:高级玩法 */std::unique_lock<std::mutex> ul(g_mtx, std::defer_lock); // 延迟加锁ul.lock();                       // 手动加锁g_count += 10;std::cout << "thread " << id << " add 10 -> " << g_count << '\n';ul.unlock();                     // 手动解锁/* 4. try_lock_for 示例(配合 timed_mutex) */std::timed_mutex tmtx;           // 支持超时的互斥量if (ul.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "thread " << id << " got timed_lock\n";ul.unlock();} else {std::cout << "thread " << id << " timeout\n";}
}int main()
{std::thread t1(worker, 1);std::thread t2(worker, 2);t1.join();t2.join();std::cout << "final count = " << g_count << '\n';return 0;
}

三、时间命名空间chrono的一些成员

(一)两个重要的类

头文件作用常用别名常用接口
std::chrono::duration<Rep, Period><chrono>表示“时间长度”seconds, milliseconds, microseconds, nanoseconds, minutes, hourscount(), +, -, *, /, %, std::chrono::duration_cast<Dst>(d)
std::chrono::time_point<Clock, Duration><chrono>表示“时间点”system_clock::time_point, steady_clock::time_point, high_resolution_clock::time_pointtime_since_epoch(), +, -, std::chrono::time_point_cast<Dst>(tp)

(二)具体用法

#include <iostream>
#include <chrono>
#include <thread>int main()
{using namespace std::chrono;/* 1. duration 常用别名 */seconds s(5);                     // 5 秒milliseconds ms(1500);            // 1500 毫秒microseconds us = 2 * ms;         // 自动类型提升std::cout << "ms.count() = " << ms.count() << '\n';      // 1500std::cout << "us.count() = " << us.count() << '\n';      // 3000000/* 2. duration_cast 强制转换 */seconds s2 = duration_cast<seconds>(ms);                 // 截断 1 秒std::cout << "s2.count() = " << s2.count() << '\n';      // 1/* 3. time_point 取现在 */steady_clock::time_point t0 = steady_clock::now();std::this_thread::sleep_for(1s);steady_clock::time_point t1 = steady_clock::now();/* 4. 时间差 = duration */auto elapsed = t1 - t0;                                    // 单位 nanosecondsstd::cout << "elapsed ms = "<< duration_cast<milliseconds>(elapsed).count() << '\n'; // ≈1000/* 5. 时间点 + 时长 = 新时间点 */auto deadline = steady_clock::now() + 500ms;std::cout << "deadline epoch = " << deadline.time_since_epoch().count() << '\n';return 0;
}
// time_point_cast
#include <iostream>
#include <ratio>
#include <chrono>int main ()
{using namespace std::chrono;// 1. 造单位:1 天 = 86400 秒typedef duration<int, std::ratio<60 * 60 * 24>> days_type;// 2. 把“现在”截断成整天time_point<system_clock, days_type> today =time_point_cast<days_type>(system_clock::now());// 3. 打印整数天数std::cout << today.time_since_epoch().count()<< " days since epoch\n";return 0;
}

四、atomic

(一)std::atomic 核心接口(store/load/exchange/CAS)

1、接口介绍

接口一句话作用默认内存序一行代码示例易错提醒
store原子写seq_csta.store(7, memory_order_release);无返回值,纯写
load原子读seq_cstint x = a.load(memory_order_acquire);永远返回副本
exchange拿旧值→换新值seq_cstbool old = flag.exchange(true);适合"抢锁"
compare_exchange_weak循环CAS(可假性失败)seq_cstwhile(!a.compare_exchange_weak(exp, exp+1));必须包while
compare_exchange_strong单次CASseq_cstok = a.compare_exchange_strong(exp, 100);别放循环里

2、实例

store / load
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>std::atomic<int> value{0};int main(){std::vector<std::thread> v;for(int i=1;i<=10;++i)v.emplace_back([i]{ value.store(i, std::memory_order_relaxed); });for(auto& t:v) t.join();std::cout << "final value = " << value.load(std::memory_order_relaxed) << '\n';
}
exchange —— 实现自旋锁
// ========== spinlock.h ==========
#pragma once
#include <atomic>class spinlock {std::atomic<bool> flag_{false};
public:void lock() noexcept {while (flag_.exchange(true, std::memory_order_acquire)) {/* 忙等 */}}void unlock() noexcept {flag_.store(false, std::memory_order_release);}// RAII 守卫class guard {spinlock& lk_;public:explicit guard(spinlock& lk) : lk_(lk) { lk_.lock(); }~guard() { lk_.unlock(); }guard(const guard&) = delete;guard& operator=(const guard&) = delete;};
};
compare_exchange_weak
#include <atomic>
#include <thread>
#include <iostream>std::atomic<long> counter{0};void inc(){long old = counter.load(std::memory_order_relaxed);while(!counter.compare_exchange_weak(old, old + 1,std::memory_order_relaxed,std::memory_order_relaxed)){ /* 重试 */ }
}int main(){std::thread t[4];for(auto& th:t) th = std::thread([]{ for(int i=0;i<250000;++i) inc(); });for(auto& th:t) th.join();std::cout << "counter = " << counter.load() << '\n';   // 1 000 000
}
compare_exchange_strong
#include <atomic>
#include <thread>
#include <iostream>std::atomic<int*> ptr{nullptr};int* get_singleton(){int* p = ptr.load(std::memory_order_acquire);if(!p){int* tmp = new int(42);if(ptr.compare_exchange_strong(p, tmp, std::memory_order_release))p = tmp;          // 我初始化成功elsedelete tmp;       // 别人抢先,释放多余对象}return p;
}int main(){std::thread t1(get_singleton), t2(get_singleton);t1.join(); t2.join();std::cout << *ptr.load() << '\n';  // 42
}
compare_exchange_strong
#include <atomic>
#include <thread>
#include <iostream>std::atomic<int*> ptr{nullptr};int* get_singleton(){int* p = ptr.load(std::memory_order_acquire);if(!p){int* tmp = new int(42);if(ptr.compare_exchange_strong(p, tmp, std::memory_order_release))p = tmp;          // 我初始化成功elsedelete tmp;       // 别人抢先,释放多余对象}return p;
}int main(){std::thread t1(get_singleton), t2(get_singleton);t1.join(); t2.join();std::cout << *ptr.load() << '\n';  // 42
}

(二)memory_order

1、为什么需要 memory_order

你可能以为代码是按顺序执行的,但实际上:
编译器和 CPU 会在不破坏单线程行为的前提下,重排指令。
这在多线程环境下会导致可见性问题:
一个线程写完数据,另一个线程却“看不见”。

2、六种meory_order选项

内存顺序作用简述
memory_order_relaxed只保证原子性,不保证顺序(最快,最危险)
memory_order_consume已废弃,不建议使用
memory_order_acquire加载时使用,禁止后续操作被重排到加载之前
memory_order_release存储时使用,禁止前面操作被重排到存储之后
memory_order_acq_rel读改写操作使用,兼具 acquire + release
memory_order_seq_cst最强顺序保证,所有线程看到一致的操作顺序(默认,最慢)

下面演示最常用有效的两种用法

memory_order_acquirememory_order_release
#include <atomic>
#include <thread>
#include <cassert>std::atomic<int> ready{0};
int data = 0;void writer() {data = 42;  // 非原子写ready.store(1, std::memory_order_release);  // 释放操作
}void reader() {while (ready.load(std::memory_order_acquire) == 0) {// 等待 writer 就绪}assert(data == 42);  // 一定能看到 data = 42
}int main() {std::thread t1(writer);std::thread t2(reader);t1.join();t2.join();return 0;
}

❌ 错误示范:使用 relaxed

void writer() {data = 42;ready.store(1, std::memory_order_relaxed);  // ❌ 没有同步保证
}

可能结果:reader 看到 ready == 1,但 data 还是 0!
原因:data = 42 被重排到 ready.store(1) 之后。

memory_order_seq_cst

优点:所有线程看到的操作顺序一致,无脑安全。
缺点:性能开销最大,不适合高性能场景。

实战建议

场景推荐 memory_order
简单计数器(无同步需求)relaxed
线程间传递数据(如 flag + 数据)release + acquire
读改写操作(如 fetch_add)acq_rel
不想动脑,追求绝对正确seq_cst(默认)

五、conditon_variable

实际就是对环境变量的一个封装。
在这里插入图片描述

std::condition_variable::wait 的带谓词版本(wait(lock, pred)),它是 C++ 标准库为“安全等待”提供的语法糖,用来一键解决两个经典问题:

  • 虚假唤醒(spurious wake-up)
  • 条件尚未成立就往下跑
// 1. 裸 wait:可能莫名其妙被唤醒
cv.wait(lck);                     // 2. 带谓词 wait:官方帮你写 while 循环
cv.wait(lck, pred);               // 等价于 ↓
/* 等价实现
while (!pred()) {cv.wait(lck);                // 真正阻塞点
}
*/
可以传入lamda表达式
cv.wait(lock, []{ return !q.empty(); });   // 队列有货才放行
接口作用阻塞条件返回
wait()阻塞直至被通知flag == falsevoid
wait_for(ms)阻塞或超时flag == false && 未超时true=触发, false=超时
notify()唤醒 1 个等待线程void
notify_all()唤醒 全部 等待线程void

六、异步操作常用接口

名称头文件角色典型接口
std::async<future>一键启动异步任务async(launch::async, func, args…)
std::promise<future>“写端”——给异步线程一个“交货”通道set_value / set_exception / get_future
std::future<future>“读端”——在主线程安全地拿结果get / wait / wait_for / valid

(一)std::async

策略标准措辞实际语义(主流实现)性能/备注
launch::async“as if in a new thread”必定创建新线程(或线程池线程)并在其中执行最可预期,开销最大
launch::deferred“deferred until get()/wait()惰性调用;不创建新线程;首次 get() 才同步执行零线程开销,但可能拖慢关键路径
launch::async | launch::deferred实现可任选上面两种这是版本(1)的默认策略! GNU/libc++ 普遍按“线程池满则用 deferred”策略行为不确定,写库时永远不要依赖
#include <iostream>
#include <future>
#include <thread>
#include <chrono>using namespace std::chrono_literals;
using clk = std::chrono::steady_clock;bool is_prime(long long x)
{std::cout << "[TID=" << std::this_thread::get_id() << "] start calc\n";for (long long i = 2; i * i <= x; ++i)if (x % i == 0) return false;return true;
}void test(const char* name, std::launch policy)
{std::cout << "\n----- " << name << " -----\n";auto t0 = clk::now();std::future<bool> fut;if (policy == (std::launch::async | std::launch::deferred))fut = std::async(is_prime, 982451653LL);          // 默认策略elsefut = std::async(policy, is_prime, 982451653LL);std::this_thread::sleep_for(200ms);                   // 故意延迟,观察是否并发std::cout << "main doing something else...\n";bool ok = fut.get();auto ms = std::chrono::duration<double, std::milli>(clk::now() - t0).count();std::cout << "result=" << ok << "  total=" << ms << " ms\n";
}int main()
{test("launch::async", std::launch::async);test("launch::deferred", std::launch::deferred);test("default (async|deferred)", std::launch::async | std::launch::deferred);return 0;
}

(二)std::promise

promise 只能读取一次,设置一次,如果需要“多次读取”怎么办?
std::shared_future,它支持 任意多次 get(),每次返回 const T&:

接口语义备注
构造函数promise() / promise(std::allocator_arg_t, Alloc)新建共享状态
promise(promise&&) noexcept移动构造原对象置空
promise(const promise&) = delete禁止拷贝
赋值promise& operator=(promise&&) noexcept移动赋值
析构~promise()若共享状态未就绪,存储 broken_promise 异常
get_future()一次性生成 std::future重复调用抛 future_error
set_value(const T&) / set_value(T&&)写入结果只能调一次
set_exception(std::exception_ptr)写入异常set_value 二选一
set_value_at_thread_exit / set_exception_at_thread_exit延迟到线程退出才就绪用于“线程局部对象先析构”场景
#include <iostream>
#include <thread>
#include <future>
#include <exception>using namespace std::chrono_literals;void worker(std::promise<int> prom)
{try{std::this_thread::sleep_for(1s);prom.set_value(42);          // 正常交货}catch (...){prom.set_exception(std::current_exception()); // 异常交货}
}int main()
{std::promise<int> prom;          // 1. 创建承诺std::future<int>  fut = prom.get_future(); // 2. 生成未来std::thread th(worker, std::move(prom));     // 3. 移交 promisestd::cout << "waiting...\n";try{int val = fut.get();         // 4. 阻塞取值std::cout << "got: " << val << '\n';}catch (const std::exception& ex){std::cout << "exception: " << ex.what() << '\n';}th.join();return 0;
}

(三)future / shared_future

方法描述返回值
get()获取异步操作结果(仅一次)Tvoid
valid()是否拥有共享状态bool
wait()阻塞到结果就绪void
wait_for(rel_time)等待一段相对时间future_status
wait_until(abs_time)等待到绝对时间点future_status
share()升级为可多次 getshared_futurestd::shared_future<T>
#include <iostream>
#include <future>
#include <thread>
#include <chrono>using namespace std::chrono_literals;int compute() { std::this_thread::sleep_for(1s); return 42; }int main() {std::future<int> fut = std::async(std::launch::async, compute);// 1. validstd::cout << "valid: " << fut.valid() << '\n';   // 1// 2. wait_forif (fut.wait_for(500ms) == std::future_status::timeout)std::cout << "not ready yet\n";// 3. waitfut.wait();                       // 阻塞到就绪// 4. get(仅一次)std::cout << "result: " << fut.get() << '\n';  // 42std::cout << "valid after get: " << fut.valid() << '\n'; // 0return 0;
}

使用shared_future多次获取。

#include <iostream>
#include <future>
#include <thread>std::string download(const std::string& url) {std::this_thread::sleep_for(std::chrono::milliseconds(500));return "data from " + url;
}int main() {std::promise<std::string> prom;std::shared_future<std::string> sf = prom.get_future().share(); // 升级// 多线程并发读同一份结果std::thread t1([sf] { std::cout << "t1: " << sf.get() << '\n'; });std::thread t2([sf] { std::cout << "t2: " << sf.get() << '\n'; });prom.set_value("https://example.com");  // 一次性交货t1.join(); t2.join();return 0;
}

(四)package_task

接口说明备注
packaged_task<R(Args...)>构造函数,包装任意可调用目标支持函数、lambda、bind、函数对象
packaged_task(packaged_task&&) noexcept移动构造 / 移动赋值不可拷贝
~packaged_task()析构未就绪则共享状态存 broken_promise
valid()是否拥有共享状态默认构造或移动后可能为 false
get_future()取出关联的 std::future<R>只能调用一次
operator()(Args...)同步执行任务,结果写入共享状态可重复调用(需 reset()
reset()重置共享状态,可再次 get_future()原状态若未就绪则置 broken_promise
swap(packaged_task&)交换底层状态noexcept
#include <iostream>
#include <future>
#include <thread>int add(int a, int b) { return a + b; }int main() {std::packaged_task<int(int, int)> task(add); // 1. 包装std::future<int> fut = task.get_future();    // 2. 拿 futuretask(7, 8);                                  // 3. 同步执行std::cout << "result = " << fut.get() << '\n'; // 4. 15return 0;
}

可以包装lamda以及bind

auto task_lambda = std::packaged_task<double(double, double)>([](double x, double y) { return std::pow(x, y); });auto task_bind = std::packaged_task<int()>(std::bind([](int x, int y){ return x * y; }, 9, 9));
http://www.dtcms.com/a/457235.html

相关文章:

  • 广东省省考备考(第一百一十八天10.8)——言语、资料分析、数量关系(强化训练)
  • 临沂网站制作页面如何查一个网站有没有做外链
  • 基于websocket的多用户网页五子棋(八)
  • Elastic 被评为 IDC MarketScape《2025 年全球扩展检测与响应软件供应商评估》中的领导者
  • 如何利用Python呼叫nexxim.exe执行电路模拟
  • APM学习(3):ArduPilot飞行模式
  • h5制作开发价目表常用seo站长工具
  • 忻州建设公司网站他达那非副作用太强了
  • pytest详细教程
  • 订单超时方案的选择
  • Redis 集群故障转移
  • 虚拟专用网络
  • 网站制作公司网站建设网站膳食管理东莞网站建设
  • Linux小课堂: 从零到上手的指南
  • DrissionPage防检测
  • 三亚官方网站建设ps如何做网页设计
  • Java体系总结——从基础语法到微服务
  • 深圳网站建设李天亮网站怎么做构成
  • Word卡顿,过很久才弹窗网络连接失败解决办法
  • 古典网站建设睢宁招标网官方
  • 告别物流乱象:商贸物流软件如何实现全流程可视化管理​
  • Ubuntu 20.04 安装mysql-5.7.9
  • 二、排版格式与注释
  • 计组2.2.1——加法器,算数逻辑单元ALU
  • 东莞网站建设公司注册郑州企业网站排名
  • 2025-10-08 Python 标准库 4——内置类型:数字类型
  • java ArrayList的add方法是插入到最后吗
  • Kotlin 判空写法对比与最佳实践
  • 如何在中国建设银行网站转账网站域名格式
  • OSI 七层模型