并发控制:同步(条件变量、信号量、生产者消费者)(三)

92 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

信号量

更衣室管理 (by E.W. Dijkstra)

做一点扩展——线程可以任意 “变出” 一个手环

  • 把手环看成是令牌
  • 得到令牌的可以进入执行
  • 可以随时创建令牌

“手环” = “令牌” = “一个资源” = “信号量” (semaphore)

  • P(&sem) - prolaag = try + decrease; wait; down; in
    • 等待一个手环后返回
    • 如果此时管理员手上有空闲的手环,立即返回
  • V(&sem) - verhoog = increase; post; up; out
    • 变出一个手环,送给管理员

信号量:实现生产者-消费者

信号量设计的重点

  • 考虑 “手环” (每一单位的 “

资源”) 是什么,谁创造?谁获取?

    • pc-sem.c
  P(&empty);   // P()返回 -> 得到手环
  printf("("); // 假设线程安全
  V(&fill);
}
void consumer() {
  P(&fill);
  printf(")");
  V(&empty);
}

  • 在 “一单位资源” 明确的问题上更好用

哲学家吃饭问题 (E. W. Dijkstra, 1960)

哲学家 (线程) 有时思考,有时吃饭

  • 吃饭需要同时得到左手和右手的叉子
  • 当叉子被其他人占有时,必须等待,如何完成同步?
    • 如何用互斥锁/信号量实现?
while (!(avail[lhs] && avail[rhs])) {//如果两个叉子都不在就要等锁释放掉
  wait(&cv, &mutex);
}
avail[lhs] = avail[rhs] = false;//在锁的保护下吧两个叉子拿起来
mutex_unlock(&mutex);//释放锁。吃饭

mutex_lock(&mutex);//上锁
avail[lhs] = avail[rhs] = true;//还叉子
broadcast(&cv);//把其他可能在等叉子的人唤醒
mutex_unlock(&mutex);

忘了信号量,让一个人集中管理叉子

“Leader/follower” - 生产者/消费者

  • 分布式系统中非常常见的解决思路 (HDFS, ...)
  send_request(id, EAT);
  P(allowed[id]); // waiter 会把叉子递给哲学家
  philosopher_eat();
  send_request(id, DONE);
}

void Twaiter() {
  while (1) {
    (id, status) = receive_request();
    if (status == EAT) { ... }
    if (status == DONE) { ... }
  }
}

忘了那些复杂的同步算法

可能会觉得,管叉子的人是性能瓶颈

  • 一大桌人吃饭,每个人都叫服务员的感觉
  • Premature optimization is the root of all evil (D. E. Knuth)过早优化是万恶之源

抛开 workload 谈优化就是耍流氓

  • 吃饭的时间通常远远大于请求服务员的时间
  • 如果一个 manager 搞不定,可以分多个 (fast/slow path)
    • 把系统设计好,使集中管理不成为瓶颈

总结

本次课回答的问题

  • Q: 如何在多处理器上协同多个线程完成任务?

Take-away message

  • 实现同步的方法
    • 条件变量、信号量;生产者-消费者问题
    • Job queue 可以实现几乎任何并行算法
  • 不要 “自作聪明” 设计算法,小心求证