携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
条件变量
同步问题:分析
任何同步问题都有先来先等待的条件。
- 等所有线程结束后继续执行,否则等待
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);
}