同步问题:分析

线程同步由条件不成立等待同步条件达成继续构成

线程 join

  • Tmain 同步条件:nexit == T
  • Tmain 达成同步:最后一个线程退出 nexit++

生产者/消费者问题

  • Tproduce 同步条件:CAN_PRODUCE (count < n)
  • Tproduce 达成同步:Tconsume count--
  • Tconsume 同步条件:CAN_CONSUME (count > 0)
  • Tconsume 达成同步:Tproduce count++

理想中的同步 API

wait_until(CAN_PRODUCE) {
  count++;
  printf("(");
}

wait_until(CAN_CONSUME) {
  count--;
  printf(")");
}

若干实现上的难题

  • 正确性
    • 大括号内代码执行时,其他线程不得破坏等待的条件
  • 性能
    • 不能 spin check 条件达成
    • 已经在等待的线程怎么知道条件被满足?

条件变量:理想与实现之间的折衷

一把互斥锁 + 一个 “条件变量” + 手工唤醒

  • wait(cv, mutex) 💤
    • 调用时必须保证已经获得 mutex
    • wait 释放 mutex、进入睡眠状态
    • 被唤醒后需要重新执行 lock(mutex)
  • signal/notify(cv) 💬
    • 随机私信一个等待者:醒醒
    • 如果有线程正在等待 cv,则唤醒其中一个线程
  • broadcast/notifyAll(cv) 📣
    • 叫醒所有人
    • 唤醒全部正在等待 cv 的线程

条件变量:实现生产者-消费者

void Tproduce() {
  mutex_lock(&lk);
  if (!CAN_PRODUCE) cond_wait(&cv, &lk);
  printf("("); count++; cond_signal(&cv);
  mutex_unlock(&lk);
}

void Tconsume() {
  mutex_lock(&lk);
  if (!CAN_CONSUME) cond_wait(&cv, &lk);
  printf(")"); count--; cond_signal(&cv);
  mutex_unlock(&lk);
}

代码演示 & 压力测试 & 模型检验

  • (Small scope hypothesis)

条件变量:正确的打开方式

同步的本质:wait_until(COND) { ... },因此:

  • 需要等待条件满足时
mutex_lock(&mutex);
while (!COND) {
  wait(&cv, &mutex);
}
assert(cond);  // 互斥锁保证条件成立
mutex_unlock(&mutex);
  • 任何改动使其他线可能被满足时
mutex_lock(&mutex);
// 任何可能使条件满足的代码
broadcast(&cv);
mutex_unlock(&mutex);