C多线程mtx与cnd

threads.h里的cnd为什么必须要配合mtx一起用?比如cnd_wait(),为什么要先获取信号量,然后再用cnd_wait()释放信号量?然后再等待?

这是一个非常棒的问题,它触及了条件变量(Condition Variables)设计的核心原因。简单来说,条件变量必须与互斥锁(mutex)配合使用,是为了避免一种称为“竞态条件(Race Condition)”的致命问题,并确保对共享数据的安全访问。

下面我们分步拆解这个问题。

1. 条件变量的核心作用

首先,要明白条件变量(cnd_t)本身不存储任何状态或数据。它不像一个计数器或一个标志位。它的唯一作用就是传递信号,让一个线程可以通知其他线程:“嘿,某个条件可能已经满足了,你们可以醒来检查一下了。”

2. “条件”是什么?

这个“条件”通常是一个基于共享数据的判断。例如:

  • “队列是否为空?” (queue.empty())
  • “缓冲区是否已满?” (buffer.full())
  • “某个任务是否已完成?” (task.completed)

这些共享数据必须被保护,否则多个线程同时读写会导致数据混乱。这就是互斥锁(mtx_t)的 job。

3. 为什么 cnd_wait 要先解锁再等待?

这是整个设计中最关键、最精妙的部分。假设我们没有互斥锁,或者 cnd_wait 不释放锁,会发生什么?

灾难场景(没有互斥锁的保护)

  1. 线程A 检查条件,发现条件不满足(例如,队列是空的)。
  2. 就在线程A准备调用 cnd_wait 之前,操作系统调度走了它,转而执行线程B
  3. 线程B 获取了数据(例如,向队列放入一个数据),修改了条件(队列不再为空),然后调用 cnd_signal 发送信号。
  4. 但此时线程A还没有进入等待状态!所以这个信号丢失了(就像你在别人还没进等候室之前就喊了一声“有你的快递”,但他根本没听到)。
  5. 线程A 恢复执行,现在它调用 cnd_wait 并进入等待。因为它错过了信号,它可能会永远等下去(死锁)。

cnd_wait 的原子操作如何解决这个问题

cnd_wait(&cond, &mtx) 的内部执行是原子性(Atomic) 的,它做了三件事:

  1. 解锁互斥锁 (mtx_unlock):释放对共享数据的锁定。
  2. 进入等待状态:阻塞当前线程,等待信号。
  3. 重新加锁 (mtx_lock):当被 cnd_signal 或 cnd_broadcast 唤醒后,在从 cnd_wait 返回之前,重新获取互斥锁。

“解锁”和“等待”这两个步骤是捆绑在一起、不可分割的(原子操作)。 这意味着,不可能出现“信号已经发出,但线程还没开始等待”的竞态条件。信号要么在等待之前发出(这时互斥锁还锁着,发送信号的线程会被阻塞),要么在等待之后发出(这时线程已经在等待,能收到信号)。

4. 正确的使用模式

因此,使用条件变量的标准范式(Pattern)如下:

c// 消费者线程
mtx_lock(&mtx); // 1. 先锁住互斥锁,保护共享数据

while (condition_is_not_true) { // 2. 检查条件(必须用循环,不能用if)
    // 条件不满足,等待。cnd_wait会原子地:解锁 -> 等待 -> 醒来后重新加锁
    cnd_wait(&cond, &mtx);
}

// 3. 条件满足,处理数据(例如,从队列取走数据)
// ... 操作共享数据 ...

mtx_unlock(&mtx); // 4. 处理完数据,释放互斥锁
c// 生产者线程
mtx_lock(&mtx); // 1. 先锁住互斥锁,保护共享数据

// 2. 修改共享数据,使条件变为真
// ... 操作共享数据 ...

// 3. 发送信号,通知可能正在等待的消费者
cnd_signal(&cond); // 或者 cnd_broadcast(&cond);

mtx_unlock(&mtx); // 4. 释放互斥锁

总结:为什么必须配合使用?

组件职责为什么需要对方
互斥锁 (mtx_t)保护共享数据。确保任何时候只有一个线程能修改“条件”所依赖的数据。需要条件变量来高效地等待条件变化,而不是通过循环检查(忙等待)浪费CPU。
条件变量 (cnd_t)传递信号。让线程可以在条件不满足时休眠,并在条件可能改变时被唤醒需要互斥锁来保护检查条件的过程修改条件的过程,避免竞态条件,并确保“检查-等待”或“修改-发信号”的原子性。

一个生动的比喻

把它们想象成一个等候室和一把钥匙

  • 共享数据:等候室里的一个公告板。
  • 互斥锁 (mtx_t):等候室的唯一一把钥匙。拿到钥匙才能进去看/修改公告板。
  • 条件变量 (cnd_t):等候室里的一个铃铛
  • 线程:人们。

流程

  1. 你想查看公告板(检查条件),必须先拿到钥匙(mtx_lock)。
  2. 你看到公告板上写着“资源不足”(条件不满足)。你不能一直占着钥匙在那儿盯着看(忙等待),这样别人就没法改公告板了。
  3. 你走进等候室,把钥匙放回门口cnd_wait 解锁),然后坐下睡觉cnd_wait 等待)。这两个动作是一气呵成的,确保了不会有人在你睡觉的时候进不来。
  4. 另一个人来了,他拿到钥匙(mtx_lock),修改了公告板(“资源已就绪”),然后摇响铃铛cnd_signal),最后放下钥匙离开(mtx_unlock)。
  5. 铃铛吵醒了你。你醒来后的第一件事就是去门口拿钥匙cnd_wait 重新加锁),然后再次查看公告板(重新检查条件)并继续工作。

如果没有这个“放下钥匙再睡觉”和“醒来先拿钥匙”的机制,整个系统就会陷入混乱和死锁。这就是为什么条件变量必须和互斥锁配合使用。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
error: Content is protected !!