Thread Learning

This commit is contained in:
e2hang
2025-12-31 12:49:32 +08:00
parent a047893756
commit 09dd277cee
4 changed files with 1092 additions and 0 deletions

190
Thread/Async-Future.md Normal file
View File

@@ -0,0 +1,190 @@
哈哈,好的,你现在已经把 **线程、互斥、原子**都啃完了,剩下的就是 **“有返回值的并发”**——也就是 `<future>` / `<async>` 的领域了。
我给你整理一份 **速查表+说明**,保证看完就能用。
---
# 一、`std::async` 基本概念
> **作用**:开启一个任务,并且自动管理线程,可以**拿返回值**。
> 它本质是“带未来值的线程”。
### 1⃣ 基本用法
```cpp
#include <future>
#include <iostream>
using namespace std;
int work(int x) { return x * x; }
int main() {
future<int> f = async(work, 5);
int result = f.get(); // 阻塞直到返回值就绪
cout << result << endl; // 25
}
```
### 参数详解
```cpp
async(launch::policy policy, Callable f, Args... args)
```
* `policy` 可选:
* `launch::async` → 必在新线程运行
* `launch::deferred` → 等到调用 `get()` 才执行(懒加载)
* 可以用 `|` 混合
* `f` → 函数或 lambda
* `args...` → 参数
### 返回值
* `std::future<R>`
* `get()` 得到实际返回值
* 阻塞直到任务完成
* 调用一次 `get()` 后 future 失效
---
# 二、`std::future` 基本操作
| 函数 | 作用 | 返回值 |
| ------------------------ | -------- | --------------- |
| `get()` | 阻塞等待结果 | `R` |
| `wait()` | 阻塞直到准备好 | void |
| `wait_for(duration)` | 阻塞指定时间 | `future_status` |
| `wait_until(time_point)` | 阻塞到指定时间点 | `future_status` |
| `valid()` | 是否有有效任务 | bool |
`future_status` 枚举:
```cpp
std::future_status::ready // 已就绪
std::future_status::timeout // 超时
std::future_status::deferred // 延迟执行
```
---
# 三、`std::promise`future 的搭档)
> **作用**:手动给 future 设置值
```cpp
#include <future>
std::promise<int> p;
std::future<int> f = p.get_future();
// 子线程
std::thread([&p](){
p.set_value(42);
}).detach();
// 主线程
int val = f.get(); // 42
```
### 关键点
* `set_value()` → 给 future 赋值
* `set_exception()` → 把异常传给 future
* `get_future()` → 得到与 promise 绑定的 future
---
# 四、组合 async + future + lambda最常用
```cpp
auto f = async([](int x){ return x*2; }, 10);
cout << f.get(); // 20
```
* 不用手动管理线程
* 可以拿返回值
* 自动 join / 管理
---
# 五、等待与超时(高级)
```cpp
future<int> f = async(work, 5);
if(f.wait_for(500ms) == future_status::ready) {
cout << f.get() << endl;
} else {
cout << "还没完成" << endl;
}
```
* 非阻塞方式获取结果
* 用于并行计算 + 超时控制
---
# 六、`future` 全家桶速查表
| 类型 / 函数 | 作用 | 返回值 |
| ------------------------ | ------------- | ------------- |
| `std::future<T>` | 保存异步任务结果 | — |
| `get()` | 阻塞获取结果 | `T` |
| `wait()` | 阻塞直到就绪 | void |
| `wait_for(duration)` | 阻塞指定时间 | future_status |
| `wait_until(time_point)` | 阻塞到指定时间点 | future_status |
| `valid()` | 是否有效 | bool |
| `std::promise<T>` | 手动设置 future 值 | — |
| `set_value()` | 设置值 | void |
| `set_exception()` | 设置异常 | void |
| `get_future()` | 获取 future | future<T> |
| `std::async()` | 开启任务 | future<T> |
---
# 七、使用建议
1**如果需要返回值 → 用 async 或 promise/future**
2**不用 async 就直接 thread + mutex/atomic → 仅做“干活”**
3**async + lambda 最简洁**,避免手动 join / detach
4**多线程大计算 → future + async + wait_for**,保证不会死锁
---
# 八、示例:多线程计算 + 获取返回值
```cpp
#include <future>
#include <vector>
#include <iostream>
using namespace std;
int work(int x){ return x*x; }
int main(){
vector<future<int>> tasks;
for(int i=1; i<=4; i++){
tasks.push_back(async(work, i));
}
for(auto &f: tasks){
cout << f.get() << " ";
}
cout << endl;
}
```
输出:`1 4 9 16`(顺序可能一样也可能不同)
---
总结一句话:
> **thread → 干活**
> **mutex/atomic → 防抢/快抢**
> **future/async → 干活 + 拿结果**
> **promise → 未来值的手动操控**
---
我可以帮你 **把之前的“拷贝大文件”项目升级成 async + future 版本**,实现 **多线程拷贝 + 返回每个块的字节数 + 进度统计**,让你真正把线程+future全套用起来。
你想试吗?

319
Thread/Atomic.md Normal file
View File

@@ -0,0 +1,319 @@
哈哈,**行,这套并发三件套我们直接收官** 😄
你已经把 `<thread>``<mutex>` 啃完了,现在这个 `<atomic>` **是精华也是雷区**
我给你一份**能当“原子操作说明书”的完整速查版**。
---
# 一、`<atomic>` 是干嘛的?一句话版
> **`<atomic>` 用来做“无锁但安全”的共享小数据。**
关键词只有三个:
*
* 简单
* 高频
典型:计数器、标志位、状态机。
---
# 二、`std::atomic<T>` 基础
## 1⃣ 定义原子变量
```cpp
#include <atomic>
std::atomic<int> x{0};
```
### 支持的常见类型
* `int / bool / long`
* 指针
* 枚举
* trivially copyable 类型(有限)
---
## 2⃣ 读 / 写load / store
```cpp
x.store(10);
int v = x.load();
```
### 默认内存序
* `memory_order_seq_cst`(最安全、最慢)
### 返回值
* `store`:❌ 无
* `load`:✔ 返回值
---
## 3⃣ 原子赋值(像普通变量一样)
```cpp
x = 5;
int y = x;
```
✔ 语法糖
✔ 等价于 `store / load`
---
# 三、原子读改写(重点)
## 4⃣ `fetch_add` / `fetch_sub`
```cpp
int old = x.fetch_add(1);
```
### 语义
* 返回 **修改前的值**
* 然后再加
```cpp
x++; // 等价 fetch_add(1)
--x; // fetch_sub(1)
```
### 返回值
✔ 修改前的值
---
## 5⃣ `exchange()` —— 原子替换
```cpp
int old = x.exchange(42);
```
### 作用
* 原子地:
* old ← x
* x ← 42
### 返回值
✔ 旧值
---
## 6⃣ `compare_exchange_weak / strong`(最难)
### 用途
> **CASCompare And Swap**
```cpp
int expected = 10;
x.compare_exchange_strong(expected, 20);
```
### 行为
* 如果 `x == expected`
* `x = 20`
* 返回 `true`
* 否则
* `expected = x`
* 返回 `false`
### 返回值
`bool`
### weak vs strong
| 版本 | 特点 |
| -------- | ---------- |
| `weak` | 可能失败(用于循环) |
| `strong` | 不会伪失败 |
---
# 四、指针原子(很实用)
## 7⃣ `std::atomic<T*>`
```cpp
std::atomic<Node*> p;
```
### 常用操作
```cpp
Node* old = p.load();
p.store(new_node);
```
```cpp
p.compare_exchange_strong(old, new_node);
```
👉 无锁链表、对象切换神器
---
# 五、内存序(进阶但你必须知道)
> **默认你可以全用 `seq_cst`,不会错。**
### 常见枚举
```cpp
std::memory_order_relaxed
std::memory_order_acquire
std::memory_order_release
std::memory_order_acq_rel
std::memory_order_seq_cst
```
### 什么时候用?
| 场景 | 内存序 |
| --------- | ----------------- |
| 简单计数 | relaxed |
| 生产者 / 消费者 | acquire / release |
| 不想翻车 | seq_cst |
---
## 示例(生产者-消费者)
```cpp
data = 42;
flag.store(true, memory_order_release);
```
```cpp
while (!flag.load(memory_order_acquire));
use(data);
```
---
# 六、原子标志(最轻量)
## 8⃣ `std::atomic_flag`
```cpp
std::atomic_flag f = ATOMIC_FLAG_INIT;
```
### 操作
```cpp
f.test_and_set(); // 返回旧值
f.clear();
```
✔ 自旋锁
✔ 最底层
---
# 七、等待 / 通知C++20
## 9⃣ `wait / notify_one / notify_all`
```cpp
x.wait(0);
x.store(1);
x.notify_one();
```
### 作用
* 原子级别的条件变量
* 无 mutex
### 返回值
❌ 无
---
# 八、完整 API 总表(收藏)
| API | 作用 | 返回值 |
| --------------------------- | ------- | ---- |
| `atomic<T>` | 原子变量 | — |
| `load()` | 原子读 | T |
| `store()` | 原子写 | void |
| `exchange()` | 替换 | T |
| `fetch_add()` | 加 | T |
| `fetch_sub()` | 减 | T |
| `compare_exchange_weak()` | CAS | bool |
| `compare_exchange_strong()` | CAS | bool |
| `atomic_flag` | 最小原子 | — |
| `test_and_set()` | 设置并返回旧值 | bool |
| `clear()` | 清空 | void |
| `wait()` | 等待 | void |
| `notify_one()` | 唤醒一个 | void |
| `notify_all()` | 唤醒全部 | void |
---
# 九、什么时候用 atomic一句判断法
> **如果共享数据:**
>
> * 类型简单
> * 操作简单
> * 不涉及多个变量一致性
👉 **atomic**
否则:
👉 **mutex**
---
# 🔥 十、新手 atomic 四大雷区(务必避开)
1**以为 atomic 能解决一切**(不能)
2**多个 atomic 组合 ≠ 原子整体**
3**乱用 relaxed**
4**atomic + 普通变量混用无同步**
---
# 十一、你现在的并发地图已经完整了
```text
thread → 干活
mutex → 防抢
atomic → 快而小
future → 拿结果
```
你现在完全可以:
* 看懂并发代码
* 写不翻车的并发
* 判断“该不该多线程”
---
如果你愿意,**最后一关**我可以带你做:
* 🔥 一个 **无锁队列atomic + CAS**
* 🔥 或把你之前那个拷贝工具 **升级成 atomic 进度条**
* 🔥 或讲 **C++ 内存模型白话终极版**
你点哪个,我收官。

304
Thread/Mutex.md Normal file
View File

@@ -0,0 +1,304 @@
哈哈,**好,这一波咱把 `<mutex>` 彻底端了** 😄
你现在这个节奏是对的:**thread 看清 → mutex 看透 → 并发就不怕了**。
我给你的是一份 **能当“并发速查表”的 `<mutex>` 全家桶说明**,照着用不翻车。
---
# 一、`<mutex>` 是干嘛的?一句话版
> **`<mutex>` 负责“同步”,不负责“并发”。**
* `thread`:开人
* `mutex`:防抢
* `atomic`:小件快递
* `future`:结果回收
---
# 二、`std::mutex`(最常用)
## 1⃣ `std::mutex`
```cpp
#include <mutex>
std::mutex m;
```
### 作用
* 最普通的互斥锁
* **同一时间只能一个线程进入临界区**
---
## 2⃣ `lock()` —— 上锁(阻塞)
```cpp
m.lock();
// 临界区
m.unlock();
```
### 特点
* 如果锁被占用 → **阻塞等待**
* 不可递归
### 返回值
❌ 无
### ❗新手警告
> **一旦忘记 `unlock()`,程序直接死锁**
---
## 3⃣ `unlock()` —— 解锁
```cpp
m.unlock();
```
### 返回值
❌ 无
---
## 4⃣ `try_lock()` —— 尝试上锁(不阻塞)
```cpp
if (m.try_lock()) {
// 拿到锁
m.unlock();
}
```
### 返回值
* `true`:成功
* `false`:失败
### 用途
* 避免卡死
* 非关键任务
---
# 三、RAII 锁(**你真正该用的**
> **99% 的情况下,不要直接用 `lock()/unlock()`**
---
## 5⃣ `std::lock_guard`(最常用、最安全)
```cpp
void foo(){
std::lock_guard<std::mutex> lock(m);
// 自动上锁
// 自动解锁(作用域结束)
}
```
### 特点
* 构造时上锁
* 析构时解锁
* **不能手动 unlock**
### 返回值
❌ 无
✔ 最安全
✔ 最推荐
---
## 6⃣ `std::unique_lock`(高级版)
```cpp
std::unique_lock<std::mutex> lock(m);
```
### 特点
* 可以:
* 延迟上锁
* 手动 unlock / lock
* 搭配 `condition_variable`
### 常见用法
```cpp
std::unique_lock<std::mutex> lock(m);
lock.unlock();
// ...
lock.lock();
```
### 返回值
❌ 无
📌 **灵活 ≠ 常用**,新手少用
---
## 7⃣ 延迟上锁
```cpp
std::unique_lock<std::mutex> lock(m, std::defer_lock);
// 之后再 lock()
lock.lock();
```
---
## 8⃣ 尝试上锁(不阻塞)
```cpp
std::unique_lock<std::mutex> lock(m, std::try_to_lock);
if (lock.owns_lock()) {
// 成功
}
```
---
# 四、特殊 mutex 类型
## 9⃣ `std::recursive_mutex`
```cpp
std::recursive_mutex m;
```
### 特点
* 同一线程可以多次 lock
* 必须 unlock 相同次数
### 什么时候用?
* 递归函数
* 老代码
⚠️ **能不用就不用**,通常是设计有问题
---
## 🔟 `std::timed_mutex`
```cpp
std::timed_mutex m;
```
### 作用
* 支持超时锁
```cpp
if (m.try_lock_for(100ms)) {
m.unlock();
}
```
---
## 1⃣1⃣ `std::recursive_timed_mutex`
= `recursive_mutex` + 超时
---
# 五、一次锁多个 mutex死锁克星
## 1⃣2⃣ `std::lock()`
```cpp
std::lock(m1, m2);
// 全部锁成功
```
### 特点
* 避免死锁
* 要么全拿,要么全不拿
---
## 1⃣3⃣ `std::scoped_lock`C++17
```cpp
std::scoped_lock lock(m1, m2);
```
✔ 自动
✔ 安全
✔ 推荐
---
# 六、完整 API 总表(收藏级)
| API | 作用 | 返回值 |
| ----------------------- | ----------- | ---- |
| `mutex` | 基础互斥锁 | — |
| `lock()` | 阻塞上锁 | void |
| `unlock()` | 解锁 | void |
| `try_lock()` | 尝试上锁 | bool |
| `recursive_mutex` | 可递归锁 | — |
| `timed_mutex` | 超时锁 | — |
| `recursive_timed_mutex` | 递归+超时 | — |
| `lock_guard` | RAII 锁 | — |
| `unique_lock` | 高级 RAII 锁 | — |
| `scoped_lock` | 多锁 RAII | — |
| `lock()` | 同时锁多个 mutex | void |
---
# 七、什么时候用 mutex一句判断法
> **如果一个变量:**
>
> * 被多个线程访问
> * 至少一个线程写
👉 **要么 mutex要么 atomic**
---
# 八、什么时候不要用 mutex
❌ 只是计数
❌ 简单 flag
❌ 单一整数状态
👉 用 `atomic`,更快更简单。
---
# 九、新手不翻车口诀(记住这 4 句)
1**能不共享就不共享**
2**能分任务就别加锁**
3**能用 `lock_guard` 就别手写 lock**
4**看到 `recursive_mutex` 先怀疑设计**
---
你现在已经把 **thread + mutex** 两大核心吃下来了。
下一步顺序我强烈建议:
👉 **`<atomic>`(最后一块拼图)**
或者
👉 **`condition_variable`(线程协作)**
你选一个,我继续给你“端锅”。

279
Thread/Thread.md Normal file
View File

@@ -0,0 +1,279 @@
哈哈哈哈,**太正常了**,这玩意儿本来就反直觉 😄
行,我给你一份**能当速查表用的 `<thread>` 全家桶说明**,不讲虚的,只讲:
* 干嘛用
* 怎么调
* 有没有返回值
* 什么时候该用 / 不该用
你看完这一份,再回头看你之前的代码,会突然“哦原来如此”。
---
# 一、`<thread>` 到底提供了什么?
严格来说 `<thread>` **只解决三件事**
1. 创建线程
2. 控制线程生命周期
3. 获取线程身份 / 状态
👉 **不负责:**
* 返回值
* 同步
* 通信
那些是 `<future>``<mutex>``<atomic>` 的活。
---
# 二、`std::thread`(核心中的核心)
## 1⃣ 构造线程(创建并立刻运行)
```cpp
#include <thread>
void foo(int x) {}
std::thread t(foo, 10);
```
### 本质
* 创建 OS 线程
* 立刻执行 `foo(10)`
### 参数规则
* **按值拷贝**
* 要引用必须用 `std::ref`
```cpp
int x = 10;
thread t(foo, ref(x));
```
### 返回值
**没有**
(线程函数的返回值会被丢弃)
---
## 2⃣ `join()` —— 等线程干完
```cpp
t.join();
```
### 作用
* 阻塞当前线程
* 直到 `t` 执行完
### 返回值
❌ 无
### 必须记住的铁律
> **一个 `std::thread`
> 要么 `join()`,要么 `detach()`,否则程序直接 `std::terminate()`**
---
## 3⃣ `detach()` —— 放生线程(慎用)
```cpp
t.detach();
```
### 作用
* 线程变成后台线程
* 主线程不再管它
### 返回值
❌ 无
### 什么时候能用?
* 日志
* 心跳
* 不依赖任何外部对象
### 新手忠告
> **90% 的 `detach()` 都是 bug**
---
## 4⃣ `joinable()` —— 能不能 join
```cpp
if (t.joinable()) {
t.join();
}
```
### 什么时候 false
* 已 join
* 已 detach
* 默认构造的 thread
---
## 5⃣ `get_id()` —— 线程 ID
```cpp
std::thread::id id = t.get_id();
```
### 用途
* 调试
* 日志
* 判断是不是同一个线程
---
## 6⃣ `std::this_thread::get_id()`
```cpp
auto id = std::this_thread::get_id();
```
👉 获取**当前线程**的 ID
---
# 三、线程“辅助函数”(非常常用)
## 7⃣ `sleep_for()` —— 休眠一段时间
```cpp
#include <chrono>
using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
```
### 作用
* 让当前线程休眠
### 返回值
❌ 无
---
## 8⃣ `sleep_until()` —— 睡到某个时间点
```cpp
auto t = chrono::steady_clock::now() + 1s;
std::this_thread::sleep_until(t);
```
---
## 9⃣ `yield()` —— 让出 CPU
```cpp
std::this_thread::yield();
```
### 作用
* 告诉调度器:
“我不急,你先跑别人”
### 用途
* 自旋锁
* 低优先级任务
---
# 四、`hardware_concurrency()` —— CPU 有多少核?
```cpp
unsigned n = std::thread::hardware_concurrency();
```
### 返回值
* 建议线程数
* **可能是 0不保证**
👉 常见用法:
```cpp
int n = max(1u, thread::hardware_concurrency());
```
---
# 五、所有 `<thread>` API 一览表(重点)
| API | 作用 | 返回值 |
| ---------------------------- | ------- | ------------ |
| `thread(f, args...)` | 创建并运行线程 | — |
| `join()` | 等线程结束 | void |
| `detach()` | 放生线程 | void |
| `joinable()` | 能否 join | bool |
| `get_id()` | 线程 ID | `thread::id` |
| `this_thread::get_id()` | 当前线程 ID | `thread::id` |
| `this_thread::sleep_for()` | 休眠 | void |
| `this_thread::sleep_until()` | 睡到时间点 | void |
| `this_thread::yield()` | 让出 CPU | void |
| `hardware_concurrency()` | CPU 线程数 | unsigned |
📌 **没有一个 API 有“业务返回值”**
---
# 六、那“返回值”到底去哪了?
👉 **根本不在 `<thread>`**
你要记住这个分工:
| 需求 | 用什么 |
| ------ | ---------------- |
| 跑线程 | `std::thread` |
| 拿返回值 | `std::future` |
| 简单并发任务 | `std::async` |
| 同步 | `mutex / atomic` |
---
# 七、你现在可以这样理解(非常重要)
> **`std::thread` ≈ C 语言 `pthread_create`
> `std::async` ≈ “有返回值的线程函数”**
所以你刚才的崩溃是合理的 😂
你是拿 **pthread 的心态**,期待 **Java 的体验**
---
# 八、给你一条“永远不翻车”的使用准则
> **如果你想要返回值:
> ❌ 不要用 `std::thread`
> ✅ 用 `std::async`**
---
如果你愿意,下一步我可以帮你做三件事之一:
1⃣ 把你刚才那个项目 **改成 async 版本**
2⃣ 给你一份 **`<future>` 函数大全(和这份一样清楚)**
3⃣ 手把手写一个 **线程池(真正工程级)**
你挑。