手搓OS-day8

同步

本篇仅仅是实现同步问题,如果你问同步问题是什么,在应付考研的时候怎么搞,我也不介意写一手()

在OS中同步的理解是非常抽象的,抽象就抽象在和我们日常的理解完全不一样。在我们日常生活中讲同步,往往是这样一个情景;

同步率100%

嗯,所以在计算机领域中,我们也会习惯性的认为同步嘛,就是两个某种主体做着一样的动作(好怪),而在计算机领域中我们平时理解的同步其实是一种比较特殊的并行。

先来一个别扭且不恰当的比喻,首先必须说明我们生活中的活动有些是瞬间完成的,有些是需要一定时间的,下面是不太正常比喻:(①)有一个前来买瓜,(②)该人询问这瓜多少钱并(③)表示“你这瓜是瓜皮子是金子做的还是瓜粒子是金子做的“,(④)然后一刀捅穿了瓜摊老板, (⑤)传出了阵阵”萨日朗“。

火红的萨日朗

上面的小故事由五个步骤组成,所有的情节发生都是有因果关系的,因此不能出现顺序的变化,不可能在捅穿老板之前就传出“萨日朗”,也不能在找茬之前就捅穿(不许抬杠),这种必须按照一定顺序去执行(需要完全执行一步,才能执行下一步)的故(程)事(序),就是同步。

说同步就不得不说一下异步,在生活中往往同步和异步是同时存在的。在一串串有逻辑相关的同步中,往往存在着异步。引用从知乎上看到的比喻:异步就是在你蒸饭期间去炒菜,也就是不必等待蒸饭结束的过程。

OK,那么为什么要提出同步和异步的问题呢?

这就不得不提一下我们之前研究过的并发技术了,因为为了榨干每一滴处理机,计算机科学家机智的想出来了用程序对处理机进行车轮战。但是这种车轮战是不可控的,我们不知道什么时候哪个程序会占用处理机,所以当处理需要必要的逻辑顺序时,同步的问题就很重要了。而异步是因为有了一大串同步,干等着又不符合计算机科学家喜欢榨干处理机的风格,因此又提出了异步问题。

实现与简单介绍

生产者-消费者问题

问题我不太会介绍,于是我决定直接剽窃一个问题介绍:

Dijkstra wrote about the unbounded buffer case: "We consider two processes, which are called the 'producer' and the 'consumer' respectively. The producer is a cyclic process and each time it goes through its cycle it produces a certain portion of information, that has to be processed by the consumer. The consumer is also a cyclic process and each time it goes through its cycle, it can process the next portion of information, as has been produced by the producer ... We assume the two processes to be connected for this purpose via a buffer with unbounded capacity."

没错就是那个Dijkstra,由于我是懒狗,所以我决定翻译也交给chatGPT(虽然我相信大家是能直接看懂的):

迪杰斯特拉 (Dijkstra)描述了无界缓冲区的情况:“我们考虑两个进程,分别称为’生产者’和’消费者’。生产者是一个循环过程,每次经过循环会产生一定量的信息,这些信息需要消费者进行处理。消费者也是一个循环过程,每次经过循环时,可以处理生产者产生的下一个信息部分…为此,我们假设这两个进程通过一个容量无限的缓冲区连接在一起。”

学过操作系统的都知道,解决同步问题用的是信号量机制。所以我们也就不去走弯路了,直奔信号量机制(当然直接调用管程也是可以的)。

停,这里他用了一个很骚的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int n,count=0;
mutex_t lk=MUTEX_INIT();
cond_t cv=COND_INIT();

#define CAN_PRODUCE (count<n)
#define CAN_CONSUME (count>n)

void Tproduce(){
while (1)
{
mutex_lock(&lk);
while (!CAN_PRODUCE)
{
cond_wait(&cv,&lk);
}
printf("(");
count++;
cond_broadcast(&cv);
mutex_unlock(&lk);
}
}
void Tconsume() {
while (1) {
mutex_lock(&lk);
while (!CAN_CONSUME) {
cond_wait(&cv, &lk);
}
printf(")"); count--;
cond_broadcast(&cv);
mutex_unlock(&lk);
}
}

这种方法称为条件变量法,有没有感觉他和前面Peterson算法的flag很像,其实就是很像,作为一个标志来放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sem_t fill, empty;

void Tproduce() {
while (1) {
P(&empty);
printf("(");
V(&fill);
}
}

void Tconsume() {
while (1) {
P(&fill);
printf(")");
V(&empty);
}
}

不细说,好理解,下一个

哲学家就餐问题

省流:操作的时候无脑mutex住,就不会出死锁bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sem_t chopsticks[5]={1,1,1,1,1};
mutex_t lk=MUTEX_INIT();

void philosopher(int i) {
while (1) {
mutex_lock(&lk);
printf("搞哲♂学");
P(&chopsticks[i+1]%5);
P(&chopsticks[i])
printf("吃");
V(&chopsticks[i+1]%5);
V(&chopsticks[i])
mutex_unlock(&lk);
}
}

总结

有用但不难,平时考察很频繁,记住要点很重要:

  1. 分析什么是互斥访问的
  2. 互斥加锁
  3. 分析什么是同步访问的
  4. 同步访问涉及什么资源,需要什么p什么v什么
  5. 添加对应的信号量
  6. 检查是否死锁,死锁则调整互斥锁与pv操作的顺序