《计算机操作系统》_并发控制:同步(条件变量、信号量、生产者消费者、哲学家吃饭问题)20251104
ok煮波躺了半个月,好多课程学不会,好多作业、好多pre,赶一起了,实在没时间更新。ok今晚没课,更新一篇。
同步(Asynchronous):多个变量在变化过程中保持一定的相对关系
异步(不同步)
1.并发程序的同步
并发程序的步调很难保持“完全同步”
线程同步:在某个时间点共同达到互相已知的状态

1.1生产者-消费者问题
99%的实际并发问题都可以用生产者、消费者问题解决
合法括号问题:
void Tproduce() { while (1) printf("("); }
void Tconsume() { while (1) printf(")"); }左括号:生产资源(任务)、放入队列。
右括号:从队列中取出资源(任务)执行。
能否使用互斥锁来实现括号问题?
#include "thread.h"
#include "thread-sync.h"int n, count = 0;
mutex_t lk = MUTEX_INIT();void Tproduce() {while (1) {
retry:mutex_lock(&lk);if (count == n) { //如果深度达到n,那么就解开锁,开始循环mutex_unlock(&lk);goto retry;}count++; //一旦条件不成立,打印左括号,深度+1printf("(");mutex_unlock(&lk);}
}void Tconsume() {while (1) {
retry:mutex_lock(&lk); //如果深度为0,不可以打印右括号,开始循环if (count == 0) {mutex_unlock(&lk);goto retry;}count--; //条件不成立的话,可以打印右括号,深度-1printf(")");mutex_unlock(&lk);}
}int main(int argc, char *argv[]) { //n的深度选择assert(argc == 2);n = atoi(argv[1]);setbuf(stdout, NULL);for (int i = 0; i < 8; i++) {create(Tproduce);create(Tconsume);}
}问题1:不管该线程是否会执行,线程都会将锁拿到自己手上(互斥)
在sum.c的程序中,join函数就是一种互斥锁
join函数(等待所有线程结束)
#include "thread.h"#define N 100000000long sum = 0;void Tsum() {for (int i = 0; i < N; i++) {sum++;}
}int main() {create(Tsum);create(Tsum);join();printf("sum = %ld\n", sum);
}任何同步问题都会有先来先等待的问题
2.条件变量API
将pc.c的自旋变成睡眠。
wait(cv, mutex)
调用时必须保证已经获得 mutex
释放 mutex、进入睡眠状态
signal/notify(cv) 💬 私信:走起
如果有线程正在等待 cv,则唤醒其中一个线程
broadcast/notifyAll(cv) 📣 所有人:走起
唤醒全部正在等待 cv 的线程错误代码:
#include "thread.h"
#include "thread-sync.h"int n, count = 0;
mutex_t lk = MUTEX_INIT();
cond_t cv = COND_INIT();void Tproduce() {while (1) {mutex_lock(&lk);if (count == n) {cond_wait(&cv, &lk);}printf("("); count++;cond_signal(&cv);mutex_unlock(&lk);}
}void Tconsume() {while (1) {mutex_lock(&lk);if (count == 0) {pthread_cond_wait(&cv, &lk);}printf(")"); count--;cond_signal(&cv);mutex_unlock(&lk);}
}int main(int argc, char *argv[]) {assert(argc == 2);n = atoi(argv[1]);setbuf(stdout, NULL);for (int i = 0; i < 8; i++) {create(Tproduce);create(Tconsume);}
}代码在只有两个线程时工作的很好,在有多个线程的时候出错,为什么呢?
两个cons睡眠,一个prod输出后唤醒一个cons。cons工作完毕后唤醒另一个cons,出现错误!不可以出现同类唤醒,需要两个条件变量。
万能的并发编程方法:
只要条件不成立,就循环睡眠
只要任何人可以使条件成立,就广播启动所有
//需要等待条件满足时
mutex_lock(&mutex);
while (!cond) {wait(&cv, &mutex);
}
assert(cond);
// ...
// 互斥锁保证了在此期间条件 cond 总是成立
// ...
mutex_unlock(&mutex);//其他线程条件可能被满足时broadcast(&cv);将串行程序更改为并行程序
struct job {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// 注意回收分配的资源
}3.信号量
class Semaphore:token, waits = 1, ''def P(self, tid): //获取钥匙if self.token > 0:self.token -= 1 //若有钥匙,我将得到一把钥匙,钥匙-1return Trueelse:self.waits = self.waits + tid //否则等待唤醒return Falsedef V(self): //变出一个钥匙来if self.waits: //如果有有人在等这个钥匙,我就不还钥匙 self.waits = self.waits[1:] //我就唤醒一个等待状态的进程else:self.token += 1 //没有人在等待的话,我就将钥匙的数量+1@threaddef t1(self):self.P('1')while '1' in self.waits: passcs = Truedel csself.V()@threaddef t2(self):self.P('2')while '2' in self.waits: passcs = Truedel csself.V()@markerdef mark_t1(self, state):if localvar(state, 't1', 'cs'): return 'blue'@markerdef mark_t2(self, state):if localvar(state, 't2', 'cs'): return 'green'@markerdef mark_both(self, state):if localvar(state, 't1', 'cs') and localvar(state, 't2', 'cs'):return 'red'
示例代码实现生产者消费者问题:
void producer() {P(&empty); // P()返回 -> 得到手环printf("("); // 假设线程安全V(&fill);
}
void consumer() {P(&fill);printf(")");V(&empty);
}在“一单位资源”明确的情况下,使用信号量是一个明智的选择
4.哲学家吃饭问题
哲学家(线程)有时候思考,有时候吃饭
吃饭的时候需要同时得到左手和右手的叉子
当叉子被其他人占有时,必须等待,如何完成同步?
如何用互斥锁/信号量实现?
失败的尝试:
#include "thread.h"
#include "thread-sync.h"#define N 3
sem_t locks[N];void Tphilosopher(int id) {int lhs = (id - 1) % N;int rhs = id % N;while (1) {P(&locks[lhs]);printf("T%d Got %d\n", id, lhs + 1);P(&locks[rhs]);printf("T%d Got %d\n", id, rhs + 1);V(&locks[lhs]);V(&locks[rhs]);}
}int main(int argc, char *argv[]) {for (int i = 0; i < N; i++) {SEM_INIT(&locks[i], 1);}for (int i = 0; i < N; i++) {create(Tphilosopher);}
}问题在于:如果所有的人同时举起一把叉子,那就所有人都无法吃饭(死锁)
成功的办法:
不要试图使用聪明的办法,尽量使用万能的办法(互斥锁和条件变量)
mutex_lock(&mutex);
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管理)
void Tphilosopher(int id) {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) { ... }}
}但是twaiter可能会忙不过来(1000w并发怎么办)
ok课程结束放个炸鸡

