《操作系统导论》(Operating Systems: Three Easy Pieces)第30章(条件变量) 作业习题程序说明

2021/10/3 17:40:57

本文主要是介绍《操作系统导论》(Operating Systems: Three Easy Pieces)第30章(条件变量) 作业习题程序说明,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

操作系统导论 :Operating Systems: Three Easy Pieces

课后习题:Homework

该章节课后习题README部分翻译(便于以后查看):

1. 习题介绍

        这个作业让你探索一些使用条件变量来实现本章讨论的生产者/消费者队列的各种形式的真实代码。您将查看实际的代码,在各种配置中运行它,并使用它来了解哪些工作,哪些不工作,以及其他的复杂情况。

代码的不同版本对应着“解决”生产者/消费者问题的不同方法。

第一步是下载代码并键入make以构建所有变体。解压后应该看到四个文件:

  • main-one-cv-while.c:用一个条件变量解决了 生产者/消费者问题。
  • main-two-cvs-if.c:同上,但有两个条件变量,并使用if检查是否休眠。
  • main-two-cvs-while.c:同上,但有两个条件变量while,用于检查是否睡眠。(正确的版本。
  • main-two-cvs-while-extra-unlock.c:同上,但释放锁,然后在填充和获取程序周围重新获取它。

查看pc-header.h也是有用的,它包含所有这些不同的主程序的公共代码,以及Makefile,以便正确地构建代码。

每个程序都有以下标志:

  -l <number of items each producer produces>
  -m <size of the shared producer/consumer buffer>
  -p <number of producers>
  -c <number of consumers>
  -P <sleep string: how producer should sleep at various points>
  -C <sleep string: how consumer should sleep at various points>
  -v [verbose flag: trace what is happening and print it]
  -t [timing flag: time entire execution and print total time]

这里,前四个参数关系是明确的:-l 指定每个生产者应该循环多少次(以及每个生产者产生多少数据项),-m 控制共享缓冲区的大小(大于等于1),-p 和 -c 分别设置有多少生产者和消费者。

        更有趣的是两个睡眠字符串(sleep string),一个用于生产者,一个用于消费者。这些标志允许你让每个线程在执行的某些点休眠,从而切换到其他线程;这样你就可以尝试各种解决方案,也许还能找出具体问题,或者研究生产者/消费者问题的其他方面。

        该字符串如下所示。例如,如果有三个生产者,字符串应该用冒号分隔符分别指定每个生产者的睡眠时间。这三个生产者的睡眠字符串看起来像这样。

sleep_string_for_p0:sleep_string_for_p1:sleep_string_for_p2 

        每个睡眠字符串依次是一个逗号分隔的列表,用于决定在代码中的每个sleep点睡眠多少时间。为了更好地理解每个生产者或每个消费者的睡眠字符串,让我们看看main-two-cvs-while.c中的代码,特别是生产者代码。在这个代码片段中,生产者线程会循环一段时间,通过调用\_fill()将元素放入共享缓冲区。在这个填充例程周围有一些等待和发送信号的代码,以确保当生产者试图填充缓冲区时缓冲区没有被填满。详情见本章。

void *producer(void *arg) {
    int id = (int) arg;
    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(i);                 p4;
        Cond_signal(&fill);         p5;
        Mutex_unlock(&m);           p6;
    }
    return NULL;
}

从代码中可以看到,有许多标记为p0、p1等的点。这些点是代码可以休眠的地方。消费者代码(这里没有显示)有类似的等待点(c0等)。

为生产者指定睡眠字符串很简单:只要确定生产者在p0, p1,…, p6。例如,字符串1,0,0,0,0,0,0,0,0 指定生产者应该在标记p0处休眠1秒(在获取锁之前),然后在每次循环中都不休眠。

2. 操作示例

        现在让我们展示运行其中一个程序的输出。首先,让我们假设用户运行时只有一个生产者和一个消费者。让我们不添加任何睡眠(这是默认行为)。在本例中,缓冲区大小被设置为2 (-m 2)。

首先,让我们构建如下代码:

homework/HW-Threads-RealCV$ make
gcc -o main-two-cvs-while main-two-cvs-while.c -Wall -pthread
gcc -o main-two-cvs-if main-two-cvs-if.c -Wall -pthread
gcc -o main-one-cv-while main-one-cv-while.c -Wall -pthread
gcc -o main-two-cvs-while-extra-unlock main-two-cvs-while-extra-unlock.c 
  -Wall -pthread

现在,我们能够运行如下程序:

homework/HW-Threads-RealCV$ ./main-two-cvs-while -l 3 -m 2 -p 1 -c 1 -v

在本例中,如果您跟踪代码(使用verbose标志-v),您将在屏幕上得到以下输出(或类似的内容)

 NF             P0 C0
  0 [*---  --- ] p0
  0 [*---  --- ]    c0
  0 [*---  --- ] p1
  1 [u  0 f--- ] p4
  1 [u  0 f--- ] p5
  1 [u  0 f--- ] p6
  1 [u  0 f--- ]    c1
  1 [u  0 f--- ] p0
  0 [ --- *--- ]    c4
  0 [ --- *--- ]    c5
  0 [ --- *--- ]    c6
  0 [ --- *--- ] p1
  0 [ --- *--- ]    c0
  1 [f--- u  1 ] p4
  1 [f--- u  1 ] p5
  1 [f--- u  1 ] p6
  1 [f--- u  1 ]    c1
  1 [f--- u  1 ] p0
  0 [*---  --- ]    c4
  0 [*---  --- ]    c5
  0 [*---  --- ]    c6
  0 [*---  --- ] p1
  0 [*---  --- ]    c0
  1 [u  2 f--- ] p4
  1 [u  2 f--- ] p5
  1 [u  2 f--- ] p6
  1 [u  2 f--- ]    c1
  0 [ --- *--- ]    c4
  0 [ --- *--- ]    c5
  0 [ --- *--- ]    c6
  1 [f--- uEOS ] [main: added end-of-stream marker]
  1 [f--- uEOS ]    c0
  1 [f--- uEOS ]    c1
  0 [*---  --- ]    c4
  0 [*---  --- ]    c5
  0 [*---  --- ]    c6

Consumer consumption:
  C0 -> 3

在描述这个简单示例中发生的事情之前,让我们先理解输出中对共享缓冲区的描述,如左边所示。一开始是空的(一个空槽用——表示,两个空槽用[*--- ---]表示);输出还显示了缓冲区中的条目数(num\_full),从0开始。

然后,在P0给它赋值后,它的状态变为[u 0 f——](num\_full变为1)。你可能还会注意到这里有一些额外的标记;u标记显示use\_ptr的位置(这是下一个消费值的消费者将从那里获得一些东西);类似地,f标记显示了填充\_ptr的位置(这是下一个生产者将产生值的位置)。当你看到*标记时,它仅仅意味着use\_ptr和fill\_ptr指向同一个槽。

沿着右侧,您可以看到每个生产者和消费者将要执行的步骤的轨迹。例如,生产者获取锁(p1),然后,因为缓冲区有一个空槽,所以在其中生成一个值(p4)。然后继续执行,直到它释放锁(p6),然后尝试重新获取锁。在本例中,使用者获取锁并使用值(c1, c4)。进一步研究跟踪,以了解生产者/消费者解决方案如何与单一生产者和消费者一起工作。

现在,让我们添加一些暂停来更改跟踪的行为。在本例中,假设我们想让生产者休眠,以便消费者可以先运行。我们可以这样做

homework/HW-Threads-RealCV$ ./main-two-cvs-while -l 1 -m 2 -p 1 -c 1 -P 1,0,0,0,0,0,0 -C 0 -v
 NF             P0 C0
  0 [*---  --- ] p0
  0 [*---  --- ]    c0
  0 [*---  --- ]    c1
  0 [*---  --- ]    c2
  0 [*---  --- ] p1
  1 [u  0 f--- ] p4
  1 [u  0 f--- ] p5
  1 [u  0 f--- ] p6
  1 [u  0 f--- ]    c3
  0 [ --- *--- ]    c4
  0 [ --- *--- ]    c5
  0 [ --- *--- ]    c6
  1 [f--- uEOS ] [main: added end-of-stream marker]
  1 [f--- uEOS ]    c0
  1 [f--- uEOS ]    c1
  0 [*---  --- ]    c4
  0 [*---  --- ]    c5
  0 [*---  --- ]    c6

Consumer consumption:
  C0 -> 1

正如您所看到的,生成器点击代码中的p0标记,然后从它的sleep规范中获取第一个值,在本例中是1,所以每个对象在尝试获取锁之前都会休眠1秒。因此,消费者开始运行,获取锁,但发现队列为空,从而进入睡眠状态(释放锁)。然后生产者运行(最终),所有的过程都如您所料。

注意:必须为每个生产者和消费者提供睡眠规格。因此,如果您创建两个生产者和三个消费者(使用-p 2 -c 3,您必须为每个指定休眠字符串(例如,-p 0:1或-c 0,1,2:0:3,3,3,1,1,1)。睡眠字符串可以比代码中睡眠点的数量短;其余的睡眠槽被初始化为零。



这篇关于《操作系统导论》(Operating Systems: Three Easy Pieces)第30章(条件变量) 作业习题程序说明的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程