目录
12.第2个版本的卫生间共享 重做第10题的卫生间共享问题。这次把优先级赋予当前使用卫生间的性别。例如,如果卫生间里面已经有女 士在使用,新来的女士即可以直接进去,即使有男士在卫生间外面等待。
到现在可以给死锁下一个正式定义了:如果有一组线程,每个线程都在等待一个事件的发生,而这个事件只 能由该组线程里面的另一线程发出,则称这组线程发生了死锁。
6.简要论述锁、睡觉与叫醒和信号量之间的逻辑关系。
锁、睡觉与叫醒以及信号量之间存在一定的逻辑关系,它们是操作系统中用于实现线程或进程同步和互斥的重要概念。
-
锁(Lock):锁是一种同步机制,用于控制对共享资源的访问。当一个线程获取到锁时,它可以进入临界区并访问共享资源,其他线程则被阻塞,直到锁被释放。锁的作用是确保在同一时间只有一个线程能够访问共享资源,从而实现互斥。
-
睡觉与叫醒(Sleep and Wakeup):睡觉与叫醒是线程或进程进行等待和唤醒的操作。当一个线程或进程无法继续执行,例如因为某个条件不满足,它可以选择进入睡眠状态(Sleep),释放所占用的资源,并等待条件满足时被唤醒(Wakeup)。睡觉与叫醒是实现线程或进程间通信和同步的一种方式。
-
信号量(Semaphore):信号量是一种计数器,用于控制对共享资源的访问。它可以用来实现线程或进程间的互斥和同步。信号量具有一个初始值,当线程或进程要访问共享资源时,需要获取信号量。如果信号量的值大于零,则线程或进程可以继续执行,同时信号量的值减一;如果信号量的值等于零,则线程或进程被阻塞,直到有其他线程或进程释放信号量。
在逻辑上,锁、睡觉与叫醒以及信号量之间存在一些关系:
- 锁通常用于实现临界区的互斥,确保在同一时间只有一个线程可以访问共享资源。
- 当某个条件不满足时,线程可以选择进入睡眠状态,并释放所持有的锁。这样可以让其他线程有机会进入临界区,执行相关操作。
- 睡觉与叫醒操作可以与锁配合使用,确保在睡眠和唤醒的过程中不会出现竞争条件。
- 信号量可以用于实现更复杂的同步和互斥机制,例如生产者-消费者问题或读者-写者问题。
需要注意的是,不同的同步机制适用于不同的场景和需求,正确使用锁、睡觉与叫醒以及信号量是确保线程或进程同步和互斥正确性的关键。
7.假定在进程通信中使用一种类似邮箱的机制。当一个进程试图往一个满箱中放东西的时候,或者从一个空 箱中拿东西的时候,该进程并不阻塞。而是得到一个错误返回码。该进程立即重复上述过程(往邮箱中放东 西或从邮箱中拿东西),直到成功为止。请问这种机制会造成竞争吗?为什么?
这种类似邮箱的机制在进程通信中使用时,会引发竞争条件。竞争条件指的是多个进程或线程同时访问共享资源,并且其最终结果取决于访问资源的顺序或时序。在这种邮箱机制中,由于进程在满箱或空箱时不会阻塞,而是通过错误返回码重复尝试操作,可能会导致多个进程同时竞争对邮箱的访问。
具体来说,当多个进程同时尝试往一个满箱中放东西时,由于该操作会得到错误返回码,进程会立即重复操作,进而导致多个进程不断竞争地尝试放东西,但都无法成功,从而浪费了系统资源和处理器时间。
同样地,当多个进程同时尝试从一个空箱中拿东西时,它们也会不断重复操作,竞争地尝试拿东西,但都会得到错误返回码,造成资源浪费。
因此,这种机制会引发竞争条件,可能导致系统资源的浪费和性能下降。为了避免竞争条件,通常需要在进程间的访问共享资源时采用同步机制,如信号量、互斥锁等,来确保资源的互斥访问和顺序执行。这样可以避免多个进程同时对邮箱进行操作,确保进程之间的有序访问和资源的正确使用。
8.有一个同学认为本书给出的管程模式存在很大缺陷。于是他提出了改进:将wait、signal和broadcast 3种原 语去掉,代之以一种新的原语:waitun-til。该新原语以一个布尔表达式作为参数,例如,waituntil x+y<z, 而锁的功能不变。请问你怎么看待这种改进的观点?
对于这种改进观点,我认为需要仔细考虑和评估。以下是一些考虑因素:
-
管程的目的和特性:管程是一种用于实现进程间同步和互斥的抽象数据类型。它的设计目标是提供一种结构化的方式来管理共享资源,并确保线程在访问资源时的互斥和同步。原始的管程模型包括wait、signal和broadcast等原语,它们在实现条件变量的等待和唤醒操作时非常有用。这些原语的存在使得线程能够更加灵活地控制资源的访问和等待条件的判断。
-
waituntil原语的局限性:将wait、signal和broadcast替换为waituntil原语可能会带来一些限制。waituntil原语只接受一个布尔表达式作为参数,用于判断是否满足等待条件。这种方式可能无法满足一些复杂的同步需求,比如等待特定的线程唤醒或等待多个条件的组合等。同时,使用布尔表达式作为参数可能需要更多的手动判断和处理,增加了编程的复杂性。
-
管程的简洁性和易用性:原始的管程模型使用wait、signal和broadcast等原语可以提供较高的可读性和可理解性。这些原语可以更直观地表达线程的等待和唤醒操作,同时也提供了清晰的语义和错误处理机制。将它们替换为waituntil原语可能会降低管程的可读性和易用性,增加了理解和维护的难度。
综上所述,虽然改进观点提出了一种新的原语waituntil,但需要仔细评估其是否能够满足复杂的同步需求,并考虑其对管程的简洁性和易用性的影响。同时,还需要考虑与现有操作系统和编程语言的兼容性,以及对现有代码和应用的影响。在实践中,选择合适的同步原语需要根据具体情况和需求进行权衡和选择。
9.下面的程序使用原子加载和存入操作来实现互斥: //全局变量 blocked[0]=false; blocked[1]=false; turn=0; //Thread 0 while(1){ blocked[0]=true; while(turn!=0){ while(blocked[1])do; turn=0; } //critical section blocked[0]=false; } //Thread 1 while(1){ blocked[1]=true; while(turn!=1){ while(blocked[0])do; turn=1; } //critical section blocked[1]=false; } 请问该实现正确吗?如果正确,请证明。否则,请给出反例。
该实现是不正确的,存在互斥性无法满足的情况。下面给出一个反例:
假设初始时,blocked[0]和blocked[1]都为false,turn为0。
- 当Thread 0执行到blocked[0] = true时,表示Thread 0进入了临界区。
- 同时,Thread 1执行到blocked[1] = true时,表示Thread 1也希望进入临界区。
- 在Thread 1的while循环中,由于turn!=1,Thread 1进入while循环。
- 然后,Thread 1在while循环中进入blocked[0]的等待。
- 此时,Thread 0执行到blocked[0] = false,表示Thread 0离开了临界区。
- Thread 0继续执行turn = 0,将turn的值设置为0。
- 此时,Thread 1的while循环条件不满足,Thread 1离开while循环,进入临界区。
- 现在,Thread 0和Thread 1都同时进入了临界区,互斥性被破坏。
因此,该实现不能保证互斥性,不是正确的解决方案。要正确实现互斥,需要使用原子操作或其他同步机制来确保在任何时候只有一个线程能够进入临界区。
10.假定n个线程要访问m个独立的共享对象。而你必须保证没有数据损坏发生。你保证的方法就是在对共享 数据的所有访问上锁。 a)请问防止数据损坏最少需要多少锁? b)如果要获得最大的并发性,需要多少锁? c)最大化并发性能否降低系统性能?
a)要防止数据损坏,最少需要m个锁,每个共享对象都有一个对应的锁。这样每个线程访问共享对象时,需要获取对应的锁,确保互斥性,防止数据损坏。
b)如果要获得最大的并发性,可以使用一个大锁来保护所有的共享对象,即只有一个锁。这样多个线程可以并发地访问不同的共享对象,只有当线程需要访问同一个共享对象时才需要竞争锁。但是这样做会导致频繁的锁竞争,可能会降低并发性能。
c)最大化并发性会增加系统的并发能力,但也可能降低系统的性能。虽然减少了锁的数量,允许更多的线程并发执行,但频繁的锁竞争可能导致线程等待和上下文切换的开销增加,从而降低系统的整体性能。因此,在设计系统时需要权衡并发性和性能,并选择合适的锁策略和同步机制来平衡二者之间的关系。
11.共享卫生间 某单位为节省经费,决定建造男女共用的单性卫生间。为满足社会风化要求,卫生间的使用需要满足如下条 件:在任何时候不同性别的人不能同时在卫生间里。 任务就是写一个程序来模拟卫生间的使用。可以使用的工具是Mesa管程。需要编写下述4个函数: woman_wants_to_enter(), man_wants_to_enter(), woman_leaves(), man_leaves(). 在这些函数里面可以使用原语lock()、unlock()、signal()、wait()和broadcast()来控制对卫生间 的使用。假定同一性别的人进入卫生间的数量不受限制。
以下是使用Mesa管程实现共享卫生间的模拟程序:
monitor Bathroom {
int occupied; // 记录卫生间内的人数
condition womanQueue, manQueue; // 两个条件变量,用于控制性别间的等待
procedure woman_wants_to_enter() {
if (occupied > 0) { // 如果卫生间内有人
wait(womanQueue); // 等待,直到卫生间内无人
}
occupied++; // 进入卫生间
}
procedure man_wants_to_enter() {
if (occupied < 0) { // 如果卫生间内有人
wait(manQueue); // 等待,直到卫生间内无人
}
occupied--; // 进入卫生间
}
procedure woman_leaves() {
occupied--; // 离开卫生间
if (occupied == 0) {
signal(manQueue); // 通知等待的男性进入
}
}
procedure man_leaves() {
occupied++; // 离开卫生间
if (occupied == 0) {
signal(womanQueue); // 通知等待的女性进入
}
}
}
Bathroom bathroom;
procedure WomanThread() {
while (true) {
bathroom.woman_wants_to_enter();
// 使用卫生间
bathroom.woman_leaves();
}
}
procedure ManThread() {
while (true) {
bathroom.man_wants_to_enter();
// 使用卫生间
bathroom.man_leaves();
}
}
在这个模拟程序中,使用了一个occupied
变量来记录卫生间内的人数,正数表示女性人数,负数表示男性人数,0表示卫生间为空。
女性线程通过调用woman_wants_to_enter()
函数尝试进入卫生间,如果卫生间内有人,则通过wait(womanQueue)
等待,直到卫生间内没有人。进入卫生间后,调用woman_leaves()
函数离开卫生间,如果卫生间内没有人,则通过signal(manQueue)
通知等待的男性进入。
男性线程同样通过类似的方式进行操作。
这样,通过使用Mesa管程中的锁、条件变量和原语,可以保证在任何时候不同性别的人不会同时在卫生间内,满足了社会风化要求。
12.第2个版本的卫生间共享 重做第10题的卫生间共享问题。这次把优先级赋予当前使用卫生间的性别。例如,如果卫生间里面已经有女 士在使用,新来的女士即可以直接进去,即使有男士在卫生间外面等待。
以下是使用Mesa管程实现优先级版本的卫生间共享模拟程序:
monitor Bathroom {
int occupied; // 记录卫生间内的人数
int womenWaiting; // 记录等待进入卫生间的女性数量
int menWaiting; // 记录等待进入卫生间的男性数量
condition womanQueue, manQueue; // 两个条件变量,用于控制性别间的等待
procedure woman_wants_to_enter() {
if (occupied < 0 || menWaiting > 0) { // 如果卫生间内有男性或有等待的男性
womenWaiting++; // 增加等待进入卫生间的女性数量
wait(womanQueue); // 等待,直到卫生间内没有男性
womenWaiting--; // 减少等待进入卫生间的女性数量
}
occupied++; // 进入卫生间
}
procedure man_wants_to_enter() {
if (occupied > 0 || womenWaiting > 0) { // 如果卫生间内有女性或有等待的女性
menWaiting++; // 增加等待进入卫生间的男性数量
wait(manQueue); // 等待,直到卫生间内没有女性
menWaiting--; // 减少等待进入卫生间的男性数量
}
occupied--; // 进入卫生间
}
procedure woman_leaves() {
occupied--; // 离开卫生间
if (occupied == 0) {
if (menWaiting > 0) {
signal(manQueue); // 通知等待的男性进入
} else if (womenWaiting > 0) {
broadcast(womanQueue); // 通知所有等待的女性进入
}
}
}
procedure man_leaves() {
occupied++; // 离开卫生间
if (occupied == 0) {
if (womenWaiting > 0) {
signal(womanQueue); // 通知等待的女性进入
} else if (menWaiting > 0) {
broadcast(manQueue); // 通知所有等待的男性进入
}
}
}
}
Bathroom bathroom;
procedure WomanThread() {
while (true) {
bathroom.woman_wants_to_enter();
// 使用卫生间
bathroom.woman_leaves();
}
}
procedure ManThread() {
while (true) {
bathroom.man_wants_to_enter();
// 使用卫生间
bathroom.man_leaves();
}
}
在这个优先级版本的模拟程序中,新增了womenWaiting
和menWaiting
两个变量,用于记录等待进入卫生间的女性和男性的数量。当一个女性线程尝试进入卫生间时,如果卫生间内有男性或有等待的男性,那么她会增加等待进入卫生间的女性数量,并等待,直到卫生间内没有男性。进入卫生间后,女性线程会减少等待进入卫生间的女性数量。当一个女性离开卫生间时,如果卫生间内没有人,那么优先通知等待的男性进入;如果卫生间内没有男性等待,但有女性等待,那么广播通知所有等待的女性进入。
男性线程同样通过类似的方式进行操作。
通过这种优先级的设计,保证了当前使用卫生间的性别有优先权进入,即使有其他性别在卫生间外等待。这样可以更好地满足社会风化要求。
13.第3个版本的卫生间共享 重做第10题。这次需要保证公平,并防止饥饿:如果女士在卫生间,则只要没有男士等待在外面,新来的女 士皆可以进入卫生间。如果有男士等待,则新来的女士就不能进去,而必须等待在男士后面。当卫生间里的 最后一位女士离开时,等待的男士可全部进入。
以下是使用Mesa管程实现公平版本的卫生间共享模拟程序:
monitor Bathroom {
int occupied; // 记录卫生间内的人数
int womenWaiting; // 记录等待进入卫生间的女性数量
int menWaiting; // 记录等待进入卫生间的男性数量
condition womanQueue, manQueue; // 两个条件变量,用于控制性别间的等待
procedure woman_wants_to_enter() {
if (occupied < 0) { // 如果卫生间内有男性
womenWaiting++; // 增加等待进入卫生间的女性数量
wait(womanQueue); // 等待,直到卫生间内没有男性
womenWaiting--; // 减少等待进入卫生间的女性数量
}
occupied++; // 进入卫生间
}
procedure man_wants_to_enter() {
if (occupied > 0) { // 如果卫生间内有女性
menWaiting++; // 增加等待进入卫生间的男性数量
wait(manQueue); // 等待,直到卫生间内没有女性
menWaiting--; // 减少等待进入卫生间的男性数量
}
occupied--; // 进入卫生间
}
procedure woman_leaves() {
occupied--; // 离开卫生间
if (occupied == 0) {
if (menWaiting > 0) {
broadcast(manQueue); // 通知所有等待的男性进入
} else if (womenWaiting > 0) {
signal(womanQueue); // 通知等待的女性进入
}
}
}
procedure man_leaves() {
occupied++; // 离开卫生间
if (occupied == 0) {
if (womenWaiting > 0) {
signal(womanQueue); // 通知等待的女性进入
} else if (menWaiting > 0) {
broadcast(manQueue); // 通知所有等待的男性进入
}
}
}
}
Bathroom bathroom;
procedure WomanThread() {
while (true) {
bathroom.woman_wants_to_enter();
// 使用卫生间
bathroom.woman_leaves();
}
}
procedure ManThread() {
while (true) {
bathroom.man_wants_to_enter();
// 使用卫生间
bathroom.man_leaves();
}
}
在这个公平版本的模拟程序中,不再区分男性和女性等待队列。当一个女性线程尝试进入卫生间时,如果卫生间内有男性,她会增加等待进入卫生间的女性数量,并等待,直到卫生间内没有男性。进入卫生间后,女性线程减少等待进入卫生间的女性数量。
当一个女性离开卫生间时,如果卫生间内没有人,并且有等待的男性,那么广播通知所有等待的男性进入。如果卫生间内没有人,并且有等待的女性,那么只通知一个女性进入。
男性线程的操作与女性线程类似。
通过这种公平的设计,保证了女性在卫生间内时不会阻塞等待的男性,只有当最后一位女性离开时,等待的男性才能进入。这样可以满足公平性的要求,并避免饥饿现象的发生。
14.读者写者问题 中国航信的航空订票数据库系统是中国国内所有航空公司的共享数据库系统。所有的旅行社订票均使用该系 统进行查询、预订和出票。由于旅行社数量众多,在任意一个时间都可能有多个进程对该数据库进行操作。 所有读写数据库的进程分为读者和写者:读者读数据库而已,写者则对数据库进行更新。为保持数据一致 性,该数据库的访问需满足如下限制: a)多个读者可以同时对数据库进行(读)操作。 b)如果有一个写者进程在对数据库进行(写)操作,则其他进程都不能对数据库进行操作。 请写出读者和写者的伪代码程序。
下面是读者和写者问题的伪代码程序:
mutex = Semaphore(1) # 用于对读者计数器的互斥访问
db = Semaphore(1) # 用于对数据库的互斥访问
readers = 0 # 读者计数器
# 读者进程
def reader():
while True:
mutex.acquire() # 互斥访问读者计数器
readers += 1 # 有一个读者进程开始读取数据库
if readers == 1:
db.acquire() # 第一个读者进程获取对数据库的访问权限
mutex.release()
# 读取数据库
# ...
mutex.acquire() # 互斥访问读者计数器
readers -= 1 # 一个读者进程读取完毕
if readers == 0:
db.release() # 最后一个读者进程释放对数据库的访问权限
mutex.release()
# 写者进程
def writer():
while True:
db.acquire() # 获取对数据库的独占访问权限
# 更新数据库
# ...
db.release() # 释放对数据库的访问权限
在上述代码中,使用了两个信号量来实现对数据库的访问控制。mutex
用于对读者计数器的互斥访问,保证多个读者同时对计数器进行操作时的正确性。db
用于对数据库的互斥访问,保证在有写者进程对数据库进行写操作时,其他进程无法访问数据库。
读者进程首先获取对读者计数器的互斥访问,然后将读者计数器加一。如果是第一个读者进程,它会获取对数据库的访问权限,然后释放对读者计数器的互斥访问。读取数据库完成后,读者进程再次获取对读者计数器的互斥访问,将读者计数器减一。如果是最后一个读者进程,它会释放对数据库的访问权限,然后释放对读者计数器的互斥访问。
写者进程直接获取对数据库的独占访问权限,进行数据库的更新操作,然后释放对数据库的访问权限。
通过这种方式,满足了多个读者可以同时读取数据库的条件,以及在有写者对数据库进行写操作时,其他进程无法对数据库进行操作的条件,从而保证了数据库的一致性。
在一个多道编程的环境里,一个系统里存在多个线程,而这些线程共享该计算机系统里的资源。因为资源竞 争而造成系统无法继续推进就难以避免了。
这里所说的资源就是一个程序工作时需要的东西:磁盘驱动器、锁、信号量、数据表格等
资源既可以是硬 件,如CPU、内存、磁盘等,也可以是软件,看不见摸不着,如锁、信号量等。根据资源是否可以抢占分 为:可抢占资源和不可抢占资源。可抢占资源当然是可以从持有者手中强行抢夺过来的资源,且不会发生系 统运行紊乱;不可抢占资源则不能从持有者手中强行抢夺。如果强行抢夺,则将造成系统运行错误。
例如,CPU是可抢占资源。因为可以将一个线程强行从CPU下拽下来,换上另一个线程来运行。被抢占的线 程可以在稍后继续运行,并正确结束。而CD刻录机就是不可抢占资源,如果在刻录光盘的中途将某个线程 赶出来,接着刻录另一个线程的数据,则该张光盘将成为废品。锁、打印机等也属于不可抢占资源。
线程在资源请求没有批准的情况下必须等待。这种等待有两种应对方式:一是阻塞等待;二是立即返回,执 行别的事情,等以后再请求。当然也可以失败退出,终止线程。就像我们在没有资源的情况下停止工作一 样。
到现在可以给死锁下一个正式定义了:如果有一组线程,每个线程都在等待一个事件的发生,而这个事件只 能由该组线程里面的另一线程发出,则称这组线程发生了死锁。
当然,上述代码并不一定会发生死锁。如果线程A先执行,获得x和y两把锁之后线程B再执行,则将不会发 生死锁。事实上,上述代码发生死锁的可能性远低于不发生死锁的可能。但在讨论死锁问题时,我们需要关 注的是死锁的可能,而不是死锁的概率。
阻挠别的线程运行的线程必定已经获得了其所需的全部资源,因而能够顺利执行到结束,并最 终释放资源。
就像人类社会, 如果每个人的资源需求都获得满足,想要什么就能获得什么,那你还不满意吗?自然不会发生竞争、争斗和 战争。遗憾的是,资源是有限的,你运气好就等到资源,运气不好就等不到,而且这种不好的运气是时常发 生的。
凡是可以进行抢占的资源,均不会发生死锁。