CHAPTER 14
Channel 函数手册
依据源码:include/Channel.h(全部逻辑在头文件模板内);src/Channel.cpp 仅 #include 该头,无额外符号。
本文说明 template <typename T> class Channel 的每个公开接口与私有状态语义。
0. 类模板概览
template <typename T>
class Channel { ... };
| 能力 | 说明 |
|---|---|
| 有缓冲 | 构造参数 buf > 0,内部 std::queue<T>,容量上限为 buf |
| 无缓冲 | buf == 0,用单个 T value + bool has_value 实现同步握手 |
| 关闭 | close() 后禁止再 push;已关闭且已读空时 pop 抛异常(或 >> 返回 false) |
| 线程安全 | 所有操作在同一把 mtx 下,配合 condition_variable cv |
类型要求 T:
- 必须可移动(
push/pop使用std::move)。 - 无缓冲模式下,类内始终存在成员
T value,因此T必须可默认构造(或编译器生成的默认构造可用)。这是与 Go channel 的重要差异之一。
1. 构造与特殊成员
explicit Channel(size_t buf = 0)
- 位置:
include/Channel.hL13 - 参数:
buf— 缓冲区容量;0表示无缓冲。 - 初始化:
buffer_size = bufclosed = falsehas_value = false(成员初始化器)
- 删除:拷贝构造、拷贝赋值、移动构造、移动赋值均
delete,通道不可转移所有权(避免内部状态与等待队列错乱)。
2. 私有数据成员(实现语义)
| 成员 | 含义 |
|---|---|
buffer_size |
容量;0 为无缓冲 |
mutable std::mutex mtx |
保护全部状态;is_closed 为 const 接口故 mutex 为 mutable |
std::condition_variable cv |
push/pop/close 上唤醒等待线程 |
std::queue<T> queue |
有缓冲时的 FIFO |
T value |
无缓冲时暂存待发/已发数据 |
bool has_value |
无缓冲时槽位是否被占用 |
bool closed |
通道是否已关闭 |
3. void push(T val)
- 位置:
include/Channel.hL20–48 - 作用:阻塞直到可以写入,然后写入数据并唤醒一个等待者。
3.1 加锁与等待条件
-
unique_lock<std::mutex> lock(mtx) -
cv.wait(lock, predicate),predicate 为:- 若
closed:立即返回true(结束等待,进入关闭分支) - 若
buffer_size == 0(无缓冲):!has_value(槽位空才能写) - 否则(有缓冲):
queue.size() < buffer_size(队列未满)
- 若
3.2 关闭后行为
- 若
closed == true:抛出std::runtime_error("push to closed channel")
(注意:关闭会使 wait 结束,但随后禁止发送。)
3.3 写入与唤醒
- 无缓冲:
value = std::move(val),has_value = true - 有缓冲:
queue.push(std::move(val)) cv.notify_one():唤醒一个可能在pop上阻塞的线程
3.4 异常
- 仅显式抛出上述
runtime_error。 - 若
T的移动抛异常,锁在unique_lock析构时释放,标准保证不泄漏互斥量;队列/槽位一致性依赖T移动异常安全(强异常安全未在文档层保证)。
3.5 面试点
- 无缓冲
push与pop形成点对点同步(类似 Go 无缓冲 channel)。 notify_one只唤醒一个线程;若多个消费者,由调度决定谁抢到锁。
4. T pop()
- 位置:
include/Channel.hL50–79 - 作用:阻塞直到可读,取出并返回元素;关闭且空时抛异常。
4.1 加锁与等待条件
-
unique_lock<std::mutex> lock(mtx) -
cv.wait(lock, predicate):- 若
closed:结束等待 - 若 无缓冲:
has_value == true - 若有缓冲:
!queue.empty()
- 若
4.2 关闭且“无数据可读”
若 closed == true 且:
- 无缓冲:
!has_value,或 - 有缓冲:
queue.empty()
则 抛出 std::runtime_error("pop from closed and empty channel")。
若关闭但仍有残留数据(有缓冲队列非空,或无缓冲仍有一次 has_value),则不走此分支,继续正常取出。
4.3 取出与唤醒
- 局部
T val - 无缓冲:
val = std::move(value),has_value = false(槽位空,允许下一次push) - 有缓冲:
val = std::move(queue.front()),queue.pop() cv.notify_one():可能唤醒阻塞在push上的生产者
4.4 返回值
- 按值返回
val(可能再触发移动)。
4.5 面试点
- “关闭 channel”语义:不能再写;读完剩余后 再读报错。
- 与 Go 类似:closed + drained 后 recv 得到零值+false;这里用异常表达失败。
5. Channel& operator<<(T val)
- 位置:
include/Channel.hL81–84 - 作用:语法糖,
push(std::move(val)),返回*this以支持链式(若你扩展多个<<)。
6. bool operator>>(T& val)
- 位置:
include/Channel.hL86–93 - 作用:非异常风格读取:成功则
val = pop()的结果并返回true;任何异常捕获后返回false(包括pop抛出的两种runtime_error)。
6.1 注意
- 吞掉所有异常:调用方无法区分“关闭且空”与其它潜在异常(若未来扩展)。
val在失败时是否被修改:成功路径赋值;失败路径catch (...)不修改val(但若pop内部在抛异常前已部分执行——当前实现是先判定再移动,失败时未移动)。
7. void close()
- 位置:
include/Channel.hL95–100 - 作用:幂等关闭通道。
7.1 步骤
unique_lock<std::mutex> lock(mtx)- 若已
closed,直接返回 closed = truecv.notify_all():唤醒所有在push/pop上等待的线程,使其重新检查谓词
7.2 唤醒后的行为
- 在
push上等待的线程:谓词因closed为真而醒来 → 随后抛push to closed channel - 在
pop上等待的线程:若已无数据 → 抛pop from closed and empty channel;若有数据则正常读完
8. bool is_closed() const
- 位置:
include/Channel.hL102–105 - 作用:在锁内读取
closed并返回。 - 线程安全:const 成员函数仍加锁,避免与
push/pop数据竞争。
9. 典型时序(无缓冲,两线程)
- 线程 A
pop:阻塞,等待has_value - 线程 B
push(x):写入value,has_value=true,notify_one - 线程 A 被唤醒,移动出
value,has_value=false,notify_one(让 B 侧若也在等可以继续——此处 B 已在 push 返回)
有缓冲时:push 在满时阻塞,pop 在非空时唤醒生产者。
10. 与 src/Channel.cpp 的关系
Channel.cpp仅包含头文件,用于某些构建系统“强制实例化”或翻译单元;无独立函数实现。- 模板方法体全在头文件,链接时由使用方实例化
Channel<T>。
11. 已知限制与改进方向(面试收尾)
| 点 | 说明 |
|---|---|
T 默认构造 |
无缓冲时 T value 成员强制要求 |
| 无超时 | 可扩展 push_for / pop_for |
operator>> |
吞异常,可改为 expected 或错误码 |
| 关闭语义 | 与 Go/Java 队列略有不同,讲清“关写不关读直到排空” |
下一模块(按索引顺序):Crash → 将追加 docs/modules/detail/Crash_FUNCTIONS.md。