非常全面的讲解捏↑
生产者-消费者问题(有限缓冲问题)
假定缓冲池有n个缓冲项,每个缓冲项能存一个数据项。
- 信号量mutex提供了对缓冲池访问的互斥要求,并初始化为1
- 信号量empty用来表示空缓冲项的个数,empty初始化为n
- 信号量full表示满缓冲项的个数,初始化为0
读者-写者问题
一个数据库可以为多个并发进程所共享。其中,有的进程可能只需要读数据库,而其他进程可能要更新(即读和写)数据库。为了区分这两种类型的进程,将前者称为读者,而将后者称为写者。
显然,如果两个读者同时访问共享数据,那么不会产生什么不利的结果。然而,如果一个写者和其他线程(既不是读者也不是写者)同时访问共享对象,很可能混乱。
该问题有多个变种,都与优先级有关。
最为简单的,通常称为第一读者-写者问题,要求没有读者需要保持等待除非已有一个写者已获得允许以使用共享数据库。换句话说,没有读者会因为有一个写者在等待而会等待其他读者的完成。
第二读者-写者问题要求,一旦写者就绪,那么写者会尽可能快地执行其写操作。换句话说,如果一个写者等待访问对象,那么不会有新读者开始读操作。对这两个问题的解答都可能导致饥饿问题。
对第一种情况,写者可能饥饿;对第二种情况,读者可能饥饿。
对于第一读者-写者问题的解决如下:
共享以下数据结构
- 信号量wrt为读者和写者进程所共用,供写者作为互斥信号量(用于读者-写者和写者-写者之间的互斥),初始化为1
- 信号量mutex用于确保在更新变量readcount时的互斥,初始化为1
- 变量readcount用来跟踪有多少进程正在读对象,初始化为0
哲学家进餐问题
生产者-消费者问题(有限缓冲问题)
适合解决描述了“缓冲区”的题目,解决的关键一是适当使用互斥锁;二是使用empty和full这样成对的信号量来进行协作。
问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为1的缓冲区,只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
问题分析:
- 关系分析。生产者和消贵者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。
- 整理思路。这里比较简单,只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步 PV 操作的位置。
- 信号量设置。信号量
mutex
作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1
:信号量full
用于记录当前缓冲池中的“满”缓冲区数,初值为0
。信号量empty
用于记录当前缓冲池中的“空” 缓冲区数,初值为n
。
代码描述为:
一个更为复杂的问题描述:桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时, 爸爸或妈妈才可向盘子中放一个水果:仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。
问题分析:这里的关系要稍复杂一些。由每次只能向其中放入一只水果可知,爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发。
father()
和 dauthger()
、mother()
和 son()
必须连续执行,正因为如此, 也只能在女儿拿走苹果后或儿子拿走橘子后才能释放盘子,即 V(plate)
操作。代码描述:
生产者-消费者问题还可以进一步约束,比如加入对生产产品数量差的限制。例如:设自行车生产线上有一个箱子,其中有N个位置(N≥3),每个位置可存放一个车架或一个车轮,又设有3名工人,其活动分别为:
解决这个问题的关键在于,为了避免死锁,我们需要对两个生产者的生产数量进行约束,以此保证箱子中不会全是某一种材料。实现如下:
另一种情况是限制了两种产物的数量差,例如:在一个仓库中可以存放A和B两种产品,要求①每次只能存入一种产品。②A产品数量-B产品数量<M。③B产品数量-A产品数量<N。
此时我们需要借助信号量维护二者之间的差距,其基本思想是记二者之差分别为信号量
Sa = M, Sb = N
的话,每当生产一个A产品,需要先保证Sa
被P一下(防止A比B多M个以上),生产完后V一下Sb
(A生产一个,B的总数要比A多N个以内,那么其生产的总数可以多一个了)。实现如下:读者-写者问题
读者-写者问题有一个关键的特征,即有一个互斥访问的计数器 count,因此遇到一个不太好解决的同步互斥问题时,要想一想用互斥访问的计数器 count 能否解决问题。
问题描述:有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作:②只允许一个写者往文件中写信息:③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让己有的读者和写者全部退出。
代码:
在上面的算法中,读进程是优先的,即当存在读进程时,写操作将被延迟,且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式会导致写进程可能长时间等待, 且存在写进程“饿死”的情况。
若希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到己在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的
writer()
和 reader()
函数中各增加一对 PV 操作,就可以得到写进程优先的解决程序:哲学家干饭问题
问题描述:一张圆桌边上坐着 5 名哲学家,每两名哲学家之间的桌上摆一根筷子,两根筷子中间是一碗米饭。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。若筷子己在他人手上,则需要等待。 饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,进餐完毕后,放下筷子继续思考。
为防止死锁发生,可对哲学家进程施加一些限制条件,比如至多允许 4 名哲学家同时进餐;仅当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子:对哲学家顺序编号,要求奇数号哲学家先拿左边的筷子,然后拿右边的筷子,而偶数号哲学家刚好相反。
采用第二种方法,可以这样实现:
吸烟者问题
问题描述:假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉己完成,此时供应者就会将另外两种材料放到桌上,如此重复(让三个抽烟者轮流地抽烟)。
问题分析:
- 关系分析。供应者与三个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。
- 整理思路。显然这里有4 个进程。供应者作为生产者向三个抽烟者提供材料。
- 信号量设置。信号量
offer1
,offer2
,offer3
分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源。信号量finish
用于互斥进行抽烟动作。
代码实现: