经典进程同步问题
1.信号量(semaphore)
1.1概念
信号量是一个计数器,常用来实现进程间的同步和互斥
1.2允许的两个操作
1)原子操作:原子操作是不会被中断的操作,要么就不干,要是干了,它就不会被任何东西打断
2)以下两个均为原子操作
3)down操作(P操作):将信号量的值值减1;当信号量等于0时,再次down操作会让之后的进程进入等待(阻塞态)
void wait(semaphore S) { --S.value; if (S.value<0) { add this process to S.L;//进程链表L,记录所有等待该资源的进程 block(S.L); } }
4)up操作(V操作):将信号量的值加1;特别的,对一个有进程在其上等待的信号量执行一次up操作后,唤醒在其上等待的其中一个进程进入就绪态
void signal(semaphore S) { ++S.value; if (S.value<=0) { remove a process P to S.L;//进程链表L,记录所有等待该资源的进程 wakeup(P); } }
1.3信号值所代表的含义
1)当信号量S大于0时表示可用的临界资源数,当等于0时,表示刚好用完;
2)标准定义,信号量只能为非负整数,但是,当信号量S小于0时,也有其意义,这个负值的绝对值表示系统中因申请该类资源而被阻塞的进程数目;
举例:有两个某类资源,四个进程A、B、C、D要用该类资源;
最开始S=2,当A进入,S=1,当B进入,S=0,表明该类资源刚好用完;
当C进入时S=-1,表明有一个进程被阻塞了,D进入,S=-2,表明有两个进程被阻塞了;
当A用完该类资源时,进行V操作,S=-1,释放该类资源,因为S<=0,表明有进程阻塞在该类资源上,于是唤醒C,C进入就绪态,C发现有一个资源可以用,然后直接转换到运行态,使用该资源;
当B用完该类资源时,进行V操作,S=-0,释放该类资源,因为S<=0,表明有进程阻塞在该类资源上,于是唤醒D,D进入就绪态,D发现有一个资源可以用,然后直接转换到运行态,使用该资源;
1.4信号量初值与其作用
1)如果初值为0,用来实现进程间的同步
2)如果初值为1,用来实现进程间互斥访问
3)如果初值为N(N一般大于1),用来限制并发数目
1.5互斥量
1)信号量的一个特殊版本
2)值为0或者1,0为加锁状态,1为不加锁状态
3)当互斥量处于锁住状态时,任何试图再次加锁的行为都将被阻塞
2.生产者-消费者问题
2.1问题描述
两个进程共享一个大小为n的缓冲区,其中一个是生产者,生产数据并将数据放入缓冲区;另一个是消费者,从缓冲区中取出数据。由于缓冲区是临界资源,如何实现缓冲区的互斥访问和生产者、消费者之间的同步?
2.2分析
使用三个信号量:
1.full,记录缓冲区满槽数目,用于同步,初值为0;
2.mutex,用于互斥,初值为1;
3.empty,记录缓冲区空槽数目,用于同步,初值为n;
2.3代码
semaphore full = 0; //缓冲区满槽数目,同步量 semaphore mutex = 1; //互斥量 semaphore empty = n; //缓冲区空槽数目 void producer() { while (1) { produce an item in nextp; down(empty); //down(empty)一定要在down(mutex)前 down(mutex); add nextp to buffer; up(mutex); up(full); } } void consumer() { while (1) { down(full); //down(full)一定要在down(mutex)前 down(mutex); remove an item from buffer; up(mutex); up(empty); } }
2.4代码解读
2.4.1同步
最开始缓冲区里没有数据,应该达到的效果是:消费者被阻塞,一旦生产者将数据送入缓冲区,消费者被唤醒去取数据,我们来看看是如何实现的:
1)消费者down(full),full=-1,根据down操作的定义(见上面代码wait函数),full<0,消费者将被送入等待链表被阻塞;
2)某时刻生产者想生产,“produce an item in nextp; down(empty); down(mutex); add nextp to buffer;up(mutex);”,一气呵成地走下来,然后up(full),full=0,根据up操作的定义(见上面代码signal函数),full<=0,消费者从等待链表中移除,然后被唤醒,变成就绪态,然后生产者彻底走完这次流程,消费者就变成运行态开始继续往下走了
反之,缓冲区满时,生产者应该被阻塞,一旦消费者取走数据,生产者才被唤醒去把数据放入缓冲区,具体实现过程类似,此处不再赘述。
2.4.2互斥
讨论互斥时,为了方便,假设现在缓冲区既不满也不空;
1)某时刻生产者想生产,“produce an item in nextp; down(empty); down(mutex);”,走到这,mutex由1变为0;
2)此时如果消费者想进来是进不来的,因为消费者“remove an item from buffer;”之前也需要down(mutex),这样的话,mutex就变成了-1,小于0,根据down操作的定义(见上面代码wait函数),mutex<0,消费者将被送入等待链表被阻塞
3)所以生产者可以安心地向下走,“add nextp to buffer;”,直到up(mutex)后,mutex=0,根据up操作的定义(见上面代码signal函数),mutex<=0,消费者从等待链表中移除,然后被唤醒,变成就绪态;
4)然后生产者彻底走完这次流程,消费者就变成运行态开始往下走。
2.4.3如果生产者和消费者把down(empty)和down(full)在down(mutex)后面,会出现问题:
1)当生产者将缓冲区放满了,消费者还没有工作,然后生产者又想生产,生产者先down(mutex)再 down(empty),mutex=0,empty=-1,根据down操作的定义(见上面代码wait函数),empty<0,生产者将被送入等待链表被阻塞,等待消费者出马取走数据;
2)然后过了一会消费者来了,先down(mutex),mutex=-1,根据down操作的定义(见上面代码wait函数),mutex<0,消费者也将被送入等待链表被阻塞;
3)于是双方都被阻塞,就陷入了无休止的等待。
3.读者-写者问题
3.1问题描述
有读者和写者两组并发程序,共读写一个文件,允许多个读者同时读,在进行写操作时不允许读操作和任何其他的写操作。
3.2分析
需要三个互斥量:
1)读者和写者互斥,需要一个互斥量readAndWrite
2)写者和写者互斥,可以借助readAndWrite
3)读者和读者不互斥,写者要等读者为0的时候才能写,所以需要一个readCount来记录读者数,并且要保证readCount在更新时不被另一个读者更新,所以还需要一个互斥量mutexReadCount
4)为了保证写请求的公平,需要一个互斥量gongpingWrite
3.3代码
1 int readCount = 0; //记录正在读的读者数 2 semaphore mutexReadCount = 1; //用于保证更新count变量时的互斥 3 semaphore readAndWrite = 1; //用于保证读和写的互斥 4 semaphore gongpingWrite = 1; //用于保证写和读的相对公平 5 void writer() 6 { 7 while (1) 8 { 9 down(gongpingWrite); //有写请求时,在写请求之后的读请求被禁止 10 down(readAndWrite); //读和写互斥 11 writing; //写操作 12 up(readAndWrite); //读和写互斥 13 up(gongPingWrite); //恢复对共享文件的访问 14 } 15 } 16 17 void reader() 18 { 19 while (1) 20 { 21 down(gongpingWrite); //有写请求时,在写请求之后的读请求被禁止 22 down(mutexCount); //互斥访问count变量 23 if (readCount == 0) //第一个读进程读共享文件时,写被禁止 24 down(readAndWrite); 25 ++readCount; 26 up(mutexReadCount); //互斥访问count变量 27 up(gongpingWrite); //恢复对共享文件的访问 28 reading; //读操作 29 down(mutexReadCount); //互斥访问readCount变量 30 --readCount; 31 if (readCount == 0) //所有读者读完,写才被允许 32 up(readAndWrite); 33 up(mutexReadCount); //互斥访问count变量 34 } 35 }
3.3代码解释
结合注释来看
1)写者要等读者数量为0的时候才能写:23行和31行
2)保证count在更新时不被另一个读者更新:22、26、29、33
3)保证写请求的公平:9、13、21、27
为什么说保证写请求的公平,如果没有这四行,假设这么一种情况,当一个读者在进行读操作时,其后连续不断的读者的读请求都将被允许,而写者的写请求将被拒绝,陷入长时间等待,等到没有新读者的请求也没有正在写的读者。这就是一种对写请求的不公平。
这四行就是来解决这个问题的:有一个读者正在进行读操作时,这时有一个写者发起写请求,它要等待count=0才能进行写操作,同时它执行第9行down(gongpingWrite),由于第21行的down(gongpingWrite),这就等于禁止了后续读者的读请求,它只需要等这个正在进行读操作的读者出来就可以了
4.哲学家就餐问题
4.1问题描述
一张圆桌上坐着5个哲学家,每个哲学家之间摆了一根筷子,筷子之间是一碗米饭;哲学家要么思考要么进餐;哲学家思考不影响他人;当哲学家进餐时,试图拿起左右两边的筷子(一根一根地拿),如果筷子在他人手里,则需等待,只有拿到两根筷子才能进餐,进餐完后又进行思考。
4.2分析
1)设置取筷子的互斥量mutex;
2)设置五个取左还是右筷子的互斥量chopstick[5];
2)对哲学家 i 编号0~4,哲学家i左边的筷子编号为 i,右边的筷子编号(i+1)%5;
4.3代码
semaphore mutex = 1; semaphore chopstick[5] = { 1,1,1,1,1 }; void P(i) { while (1) { down(mutex); //锁住取左筷子和右筷子的动作 down(chopstick[i]); down(chopstick[(i + 1) % 5]); up(mutex); //解锁,现在其他人可以试着取左筷子和右筷子了,注意:是“试着” eat; up(chopstick[i]); up(chopstick[(i + 1) % 5]); think; } }
4.4代码解读
1)使用互斥量mutex的原因:防止死锁
如果没有mutex,当五个哲学家都想要进餐,分别拿起他们左边的筷子,均执行down(chopstick[i]),chopstick[i]均变为0,筷子被拿光,当他们拿右边的筷子时,执行down(chopstick[(i+1)%5]),chopstick[i]均变为-1,小于0,根据down操作的定义(见上面代码wait函数),五个哲学家将全部被送入等待链表被阻塞,出现死锁。
posted on 2018-10-08 22:24 JoeChenzzz 阅读(315) 评论(0) 编辑 收藏 举报