《操作系统导论》第30章课后答案

30.1

问题描述:

我们的第一个问题集中在 main-two-cvs-while.c(有效的解决方案)上。 首先,研究代码。 你认为你了解当你运行程序时会发生什么吗?

解答:

我们首先查看main-two-cvs-while.c的主要函数:

void do_fill(int value) {
    // ensure empty before usage
    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
    buffer[fill_ptr] = value;
    fill_ptr = (fill_ptr + 1) % max;
    num_full++;
}
int do_get() {
    int tmp = buffer[use_ptr];
    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
    buffer[use_ptr] = EMPTY; 
    use_ptr = (use_ptr + 1) % max;
    num_full--;
    return tmp;
}
void *producer(void *arg) {
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {   p0;
	Mutex_lock(&m);             p1;
	while (num_full == max) {   p2;
	    Cond_wait(&empty, &m);  p3;
	}
	do_fill(base + i);          p4;
	Cond_signal(&fill);         p5;
	Mutex_unlock(&m);           p6;
    }
    return NULL;
}                                                                             
void *consumer(void *arg) {
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) { c0;
	Mutex_lock(&m);            c1;
	while (num_full == 0) {    c2;
	    Cond_wait(&fill, &m);  c3;
        }
	tmp = do_get();            c4;
	Cond_signal(&empty);       c5;
	Mutex_unlock(&m);          c6;
	consumed_count++;
    }

    // return consumer_count-1 because END_OF_STREAM does not count
    return (void *) (long long) (consumed_count - 1);
}

可以发现,就是书上254页的生产者/消费者线程函数。

这里,生产者生产loop个数据。生成过程如下:首先获取一把锁,然后如果缓冲区已满,则等待消费者发出的消费信号再往下执行。否则,就往缓冲区中放置生产的数据。然后发出一个信号,通知消费者消费。最后解锁。
消费者消费若干个数据。消费过程如下:首先获取一把锁,然后如果缓冲区中没有数据,则等待一个数据信号。否则,取出缓冲区中的数据。并发出一个信号。然后释放锁,并让自己的消费计数器+1。

这个代码目的是让生产者生产数据到缓冲区中(如果缓冲区未满),消费者从缓冲区中取数据。程序运行时,希望生产者会在仓库满时等待,并且一旦仓库有空间,则立即解除等待放置数据。消费者会在仓库为空的时候等待,并且仓库一旦有数据,则立刻解除等待消费数据。

30.2

问题描述:

指定一个生产者和一个消费者运行,并让生产者产生一些元素。 缓冲区大小从 1 开始,然后增加。随着缓冲区大小增加,程序运行结果如何改变? 当使用不同的缓冲区大小(例如 -m 10),生产者生产不同的产品数量(例如 -l 100), 修改消费者的睡眠字符串(例如 -C 0,0,0,0,0,0,1),full_num 的值如何变化?

解答:

首先对参数选项进行解释:

  • -p x表示指定生产者数量为x
  • -c x表示指定消费者数量为x
  • -m x表示指定buffer大小为x
  • -l x表示生产多少
  • -v跟踪运行的过程

而在下面的运行结果中,NF表示num_full,缓冲区中数据的数据量。中间是仓库的情况。- - -表示该仓库位没有数据。后面P0列表示生成者0执行到哪一行代码。对应第一题中每一行代码后面的注释。

首先假设生产者生产总数为3,缓冲区大小为1时,输入命令./main-two-cvs-while -l 3 -m 1 -p 1 -c 1 -v
在这里插入图片描述
缓冲区大小为2时:
在这里插入图片描述
缓冲区大小为3时:
在这里插入图片描述
可以看到,程序的运行结果基本不变,m的增加,只是让生成者和消费者每次放置数据的位置和消费数据的有所改变。但消费者还是能够在仓库为空时等待,一旦不为空则开始消费。生成者放置数据没有异常,消费者消费数据也没有异常。最终消费者成功地消费了3个数据。NF(full_num,缓冲区数据的数量)的值也是0,1,0,1的交替

而当当将buffer的大小设置为10(即-m 10),生产的总数设置为100(即-l 100),同时设置消费者的睡眠情况为-C 0,0,0,0,0,0,1(也就是在c6处进入睡眠)时,输入命令./main-two-cvs-while -p 1 -c 1 -m 10 -l 100 -C 0,0,0,0,0,0,1 -v
在这里插入图片描述

可以看到,在这个设置下,消费者会等待仓库满的时候才开始消费。一旦消费一个,生产者就会立刻生成一个让仓库重新变成满。直到最后生成者不生产了,消费者才开始把仓库清空。

30.4

问题描述:

我们来看一些 timings。 对于一个生产者,三个消费者,大小为 1 的共享缓冲区以及每个消费者在 c3 点暂停一秒,您认需要执行多长时间? (./main-two-cvs-while -p 1 -c 3 -m 1 -C 0,0,0,1,0,0,0:0,0,0,1,0,0,0:0,0,0,1,0,0,0 -l 10 -v -t)

解答:

输入命令./main-two-cvs-while -p 1 -c 3 -m 1 -C 0,0,0,1,0,0,0:0,0,0,1,0,0,0:0,0,0,1,0,0,0 -l 10 -v -t查看结果

下面是消费者先执行的情况:
在这里插入图片描述
由题目我们知道,设置了3个消费者,每个消费者在c3的时候进入睡眠,睡眠的时长为1秒,以上面运行出的结果为例,对花费的总时长进行分析,本次运行率先运行的是消费者线程,由于buffer中没有可以取的数据,所以三者在c2的时候进入等待fill信号的状态,之后生产者线程进行数据的填充(“生产”),当buffer中有数据的时候,消费者就可以继续运行了,但每次到达c3都要睡眠1秒钟,可以看到整个过程中,3个消费者总共到达c3的次数为13次,所以总睡眠时长为13秒,由于程序运行本身花费时间相比较13秒来说非常的小,所以程序总共花费时间近似为13秒。

查看结果:
在这里插入图片描述
可以看到结果与分析一致,程序总花费时间近似13秒。

如果生产者者线程先执行,那么睡眠时间为 12s,那么程序运行时间应该也近似12秒。

30.8

问题描述:

现在让我们看一下 main-one-cv-while.c。您是否可以假设只有一个生产者, 一个消费者和一个大小为 1 的缓冲区,配置一个睡眠字符串,让代码运行出现问题

解答:

这种生产者/消费者方案存在的问题在于“只使用了一个条件变量”,
但是当消费者数量为1时,消费者每次只能唤醒生产者,避免了这个问题,所以当生产者和消费者的数量都为1时,不会出现问题。

30.9

问题描述:

现在将消费者数量更改为两个。 为生产者消费者配置睡眠字符串,从而使代码运行出现问题。

解答:

可以发现就是书中255页的冲突问题。所以即使不设置睡眠字符串,代码运行也可能出现问题。如:生产者生产后,缓冲区满了,唤醒了两个正在睡眠的消费者中的一个,然后进入睡眠(Mutex_lock),消费者消费后,唤醒另一个消费者,进入睡眠(Mutex_lock),新的消费者线程被唤醒,发现缓冲区为空,进入睡眠(Cond_wait),此时三个线程都进入睡眠,因此程序出现错误。

输入命令./main-one-cv-while -p 1 -c 2 -m 1 -P 0,0,0,0,0,0,1 -l 2 -v -t查看运行情况:
在这里插入图片描述
可以看到,最后三者都进入睡眠

30.10

问题描述:

现在查看 main-two-cvs-if.c。 您是否可以配置一些参数让代码运行出现问题? 再次考虑只有一个消费者的情况,然后再考虑有一个以上消费者的情况。

解答:

由30.8可以得知,一个生产者一个消费者不会出现问题。

两个消费者的情况下,会出现生产者生产完成时,消费者 c1 还没有进入临界区,消费者 c2 在 Cond_wait 处等待,生产者唤醒一个消费者,c1 抢先执行,执行完后缓冲区为空,c2 开始执行,发现缓冲区为空,do_get 执行发生错误。

运行命令./main-two-cvs-if -p 1 -P 0,1,1,1,0,0,1 -c 2 -m 1 -C 0,0,0,1,0,0,1:0,0,0,1,0,0,1 -l 4 -v查看结果:
在这里插入图片描述
可以看到当一个消费者将buffer中的数据消费之后,另一个消费者再次尝试获取数据,将会发现无数据可取,导致断言触发。

30.11

问题描述:

最后查看 main-two-cvs-while-extra-unlock.c。在向缓冲区添加或取出元素时释放锁时会出现什么问题? 给定睡眠字符串来引起这类问题的发生? 会造成什么不好的结果?

解答:

首先查看一下代码:

void *producer(void *arg) {
    int id = (int) arg;
    // make sure each producer produces unique values
    int base = id * loops; 
    int i;
    for (i = 0; i < loops; i++) {   p0;
	Mutex_lock(&m);             p1;
	while (num_full == max) {   p2;
	    Cond_wait(&empty, &m);  p3;
	}
	Mutex_unlock(&m);
	do_fill(base + i);          p4;
	Mutex_lock(&m);
	Cond_signal(&fill);         p5;
	Mutex_unlock(&m);           p6;
    }
    return NULL;
}
                                                                               
void *consumer(void *arg) {
    int id = (int) arg;
    int tmp = 0;
    int consumed_count = 0;
    while (tmp != END_OF_STREAM) { c0;
	Mutex_lock(&m);            c1;
	while (num_full == 0) {    c2;
	    Cond_wait(&fill, &m);  c3;
        }
	Mutex_unlock(&m);
	tmp = do_get();            c4;
	Mutex_lock(&m);
	Cond_signal(&empty);       c5;
	Mutex_unlock(&m);          c6;
	consumed_count++;
    }

可以看到,do_get 和 do_fill 分别在消费者和生产者的锁外面,锁相当于没有加,可能导致别的线程插入进来运行,导致该线程再次get()的时候结果无数据可取,导致断言触发!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值