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

42 阅读2分钟

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

条件变量

同步问题:分析

任何同步问题都有先来先等待的条件。

线程 join (thread.hsum.c)

  • 等所有线程结束后继续执行,否则等待

NPY 的例子

  • 打完游戏且洗完头后继续执行 date(),否则等待

生产者/消费者问题

  • 左括号:深度 k
  • 右括号:k>0 时 printf,否则等待
    • 再看一眼 pc.c

Conditional Variables (条件变量, CV)

把 pc.c 中的自旋变成睡眠

  • 在完成操作时唤醒

条件变量 API

  • wait(cv, mutex) 💤
    • 调用时必须保证已经获得 mutex
    • 释放 mutex、进入睡眠状态
  • signal/notify(cv) 💬 私信:走起
    • 如果有线程正在等待 cv,则唤醒其中一个线程
  • broadcast/notifyAll(cv) 📣 所有人:走起
    • 唤醒全部正在等待 cv 的线程

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

  mutex_lock(&lk);//上锁
  if (count == n) cond_wait(&cv, &lk);//等待
  printf("("); count++; cond_signal(&cv);//被叫醒以后,count++,然后唤醒另外一个线程。
  mutex_unlock(&lk);//释放锁
}

void Tconsume() {//消费者
  mutex_lock(&lk);
  if (count == 0) cond_wait(&cv, &lk);
  printf(")"); count--; cond_signal(&cv);
  mutex_unlock(&lk);
}

压力测试:pc-cv.c

  • (Small scope hypothesis)

条件变量:条件变量是一个显式队列,当某些执行条件不满足时,线程可以把自己加入队列,等待该条件。当状态被改变了的时候,也就是另外某个线程,当它改变了上述状态时,让条件变得满足,就可以唤醒一个或者多个等待线程(通过在该条件上发信号),让它们继续执行。

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

需要等待条件满足时

mutex_lock(&mutex);
while (!cond) {
  wait(&cv, &mutex);
}
assert(cond);
// ...
// 互斥锁保证了在此期间条件 cond 总是成立
// ...
mutex_unlock(&mutex);

其他线程条件可能被满足时

broadcast(&cv);

条件变量:实现并行计算

  void (*run)(void *arg);
  void *arg;
}

while (1) {
  struct job *job;

  mutex_lock(&mutex);
  while (! (job = get_job()) ) {
    wait(&cv, &mutex);
  }
  mutex_unlock(&mutex);

  job->run(job->arg); // 不需要持有锁
                      // 可以生成新的 job
                      // 注意回收分配的资源
}

条件变量:更古怪的习题/面试题

有三种线程,分别打印 <, >, 和 _

  • 对这些线程进行同步,使得打印出的序列总是 <><_ 和 ><>_ 组合

使用条件变量,只要回答三个问题:

  • 打印 “<” 的条件?
  • 打印 “>” 的条件?
  • 打印 “_” 的条件?

#define LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))

enum { A = 1, B, C, D, E, F, };

struct rule {
  int from, ch, to;
};

struct rule rules[] = {
  { A, '<', B },
  { B, '>', C },
  { C, '<', D },
  { A, '>', E },
  { E, '<', F },
  { F, '>', D },
  { D, '_', A },
};
int current = A, quota = 1;

pthread_mutex_t lk   = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;

int next(char ch) {
  for (int i = 0; i < LENGTH(rules); i++) {
    struct rule *rule = &rules[i];
    if (rule->from == current && rule->ch == ch) {
      return rule->to;
    }
  }
  return 0;
}

void fish_before(char ch) {
  pthread_mutex_lock(&lk);
  while (!(next(ch) && quota)) {
    // can proceed only if (next(ch) && quota)
    pthread_cond_wait(&cond, &lk);
  }
  quota--;
  pthread_mutex_unlock(&lk);
}

void fish_after(char ch) {
  pthread_mutex_lock(&lk);
  quota++;
  current = next(ch);
  assert(current);
  pthread_cond_broadcast(&cond);
  pthread_mutex_unlock(&lk);
}

const char roles[] = ".<<<<<>>>>___";

void fish_thread(int id) {
  char role = roles[id];
  while (1) {
    fish_before(role);
    putchar(role); // can be long; no lock protection
    fish_after(role);
  }
}

int main() {
  setbuf(stdout, NULL);
  for (int i = 0; i < strlen(roles); i++)
    create(fish_thread);
}