Channel 函数手册
C++ 后端服务器框架Channel 函数手册
CHAPTER 14

Channel 函数手册

6 分钟阅读 · 1,527 字

依据源码: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.h L13
  • 参数:buf — 缓冲区容量;0 表示无缓冲。
  • 初始化:
    • buffer_size = buf
    • closed = false
    • has_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.h L20–48
  • 作用:阻塞直到可以写入,然后写入数据并唤醒一个等待者。

3.1 加锁与等待条件

  1. unique_lock<std::mutex> lock(mtx)

  2. 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.h L50–79
  • 作用:阻塞直到可读,取出并返回元素;关闭且空时抛异常。

4.1 加锁与等待条件

  1. unique_lock<std::mutex> lock(mtx)

  2. 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.h L81–84
  • 作用:语法糖,push(std::move(val)),返回 *this 以支持链式(若你扩展多个 <<)。

6. bool operator>>(T& val)

  • 位置:include/Channel.h L86–93
  • 作用:非异常风格读取:成功则 val = pop() 的结果并返回 true;任何异常捕获后返回 false(包括 pop 抛出的两种 runtime_error)。

6.1 注意

  • 吞掉所有异常:调用方无法区分“关闭且空”与其它潜在异常(若未来扩展)。
  • val 在失败时是否被修改:成功路径赋值;失败路径 catch (...) 不修改 val(但若 pop 内部在抛异常前已部分执行——当前实现是先判定再移动,失败时未移动)。

7. void close()

  • 位置:include/Channel.h L95–100
  • 作用:幂等关闭通道。

7.1 步骤

  1. unique_lock<std::mutex> lock(mtx)
  2. 若已 closed,直接返回
  3. closed = true
  4. cv.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.h L102–105
  • 作用:在锁内读取 closed 并返回。
  • 线程安全:const 成员函数仍加锁,避免与 push/pop 数据竞争。

9. 典型时序(无缓冲,两线程)

  1. 线程 A pop:阻塞,等待 has_value
  2. 线程 B push(x):写入 value,has_value=true,notify_one
  3. 线程 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。

全部章节