《操作系统导论》第三十章作业

第一题

在这里插入图片描述
首先,查看main-two-cvs-while.c的代码:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <string.h>

#include "common.h"
#include "common_threads.h"
#include "pc-header.h"

pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;

#include "main-header.h"

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;
	if (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;
	if (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);
}

// must set these appropriately to use "main-common.c"
pthread_cond_t *fill_cv = &fill;
pthread_cond_t *empty_cv = &empty;

// all codes use this common base to start producers/consumers
// and all the other related stuff
#include "main-common.c"

通过代码中的四个方法:do_fill()、do_get()、producer()、comsumer()我们可以知道,这是使用条件变量解决生产者/消费者的方案。
do_fill()函数:

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++;
}

这个函数就是生产进程进行生产的过程,如果缓冲区未满,就会往里面“生产”,供给消费者进行消费,同时fill_ptr=(gill_ptr+1)%max是为了形成一个循环的数组,也就是当这个数是数组的最后一位时,下一位就是数组的第一位。
do_get函数:

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;
}

与生产者相对应的就是消费者,调用这个函数来实现消费者消费的过程。数据会被从缓冲区中取出。
producer函数:

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;
	if (num_full == max) {      p2;
	    Cond_wait(&empty, &m);  p3;
	}
	do_fill(base + i);          p4;
	Cond_signal(&fill);         p5;
	Mutex_unlock(&m);           p6;
    }
    return NULL;
}

在上面的代码中,生产者线程等待条件变量empty,发信号给fill,同时生产者生产到时缓冲区满为止;
comsumer()函数:

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;
	if (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);
}

在上面的代码中,消费者线程等待条件变量fill,发信号给empty,使用了两个条件变量,这样,消费者不会再唤醒消费者,生产者也不会再唤醒生产者。

第二题

在这里插入图片描述
缓冲区大小从1开始即buffer的大小从1开始,同时由于是指定一个生产者和一个消费者运行,所以生产者和消费者的数量都为1。
为了运行程序,首先使用make命令来进行编译,然后输入指令./main-two-cvs-while -p 1 -c 1 -m 1 -l 3 -v运行程序:
使用make命令之后,生成了如下的可执行文件:
在这里插入图片描述
接下来,运行指令之后可以得到:
buffer为1的结果如下:
在这里插入图片描述

buffer结果为2的结果为:
在这里插入图片描述
buffer的结果为3的时候,结果如下:
在这里插入图片描述
可以看到对于不同大小的buffer,每次运行的结果并非都是相同的,这是因为线程的调度顺序并不是固定的,有可能生产者线程先得到调度,消费者线程后得到调度,也可能相反;当改变buffer的大小时,对于生产者来说,每次可能进行的生产循环上限将增加。但是,生产的数量并没有增加,所以总体上来说程序的运行结果并没有很大的变化。
当将buffer的大小设置为10(即-m 10),生产的总数设置为100(即-l 100),同时设置消费者的睡眠情况为-C 0,0,0,0,0,0,1(也就是在c6处进入睡眠),根据上述分析可以知道,在生产者线程,可以生产的最大数量为10,所以我们可以预测,处于生产者线程时,最大能生产的数量为10,而处于消费者线程时,每次最多消费1个数据,因为每次到达c6必须进入睡眠,无论队列中是否消费完。
输入命令:main-two-cvs-while -p 1 -c 1 -m 10 -l 100 -C 0,0,0,0,0,0,1 -v,可以得到如下的结果:
在这里插入图片描述
上图,是运行的部分结果,可以看出与我们的分析相同。说明我们的分析是正确的。

第四题

在这里插入图片描述
输入指令:./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可以可以看到如下结果:
在这里插入图片描述
由题可知,我们设置了三个消费者,但是由于生产者先进行生产,所以第一个消费者消费第一次的时候不需要进入睡眠,同时三个消费者一共消费13次,除去第一次不需要进行睡眠,另外12次都需要进入睡眠,每次睡眠需要一秒,所以一共需要12秒的睡眠时间。运行的时间比起12秒来说要小的很多。所以最后的结果可以近似为12秒。
我们查看程序的运行的结果为:
在这里插入图片描述
经过验证,可以知道我们的分析是正确的。

第八题

在这里插入图片描述
查看main-one-cv-while.c的代码如下:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <string.h>

#include "common.h"
#include "common_threads.h"

#include "pc-header.h"

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

#include "main-header.h"

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(&cv, &m);     p3;
	}
	do_fill(base + i);          p4;
	Cond_signal(&cv);           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(&cv, &m);    c3;
        }
	tmp = do_get();            c4;
	Cond_signal(&cv);          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);
}

// must set these appropriately to use "main-common.c"
pthread_cond_t *fill_cv = &cv;
pthread_cond_t *empty_cv = &cv;

// all codes use this common base to start producers/consumers
// and all the other related stuff
#include "main-common.c"

通过,与第一题的代码进行对比,我们知道第一题使用两个信号量来控制生产和消费,也就是生产者不能唤醒生产者,消费者不能唤醒消费者。但是本题的代码会出现,当两个消费者出现的时候,会发生当一个消费者消费一个数据之后,这时候缓冲区应该是空的。所以应该调用生产者来生产,但是这时候由于使用的是统一信号,无法确认应该唤醒什么进程,到时候可能出现全部都在睡眠的情况。但是,在只有一个生产者和一个消费者的时候,并不会出现这种情况。
所以,代码运行不会出现错误。(在题目所给情况下)

第九题

在这里插入图片描述
我们可以根据上一题中的分析,我们可以构造一个让该问题突出的睡眠序列:
./main-one-cv-while -p 1 -P 0,0,0,1000,0,0,1000 -c 2 -m 1 -C 0,0,0,1000,0,0,1000:0,0,0,1000,0,0,1000 -l 2 -v

解释上述的序列含义:
理论上“睡眠”状态,在被唤醒之前是不会醒过来的,为了模拟这种效果,把每次睡眠的时长设置为1000,对于生产者,当buffer已满(p3)和生产过程结束时(p6)将进入睡眠状态,所以在p3和p6处设置睡眠,对于消费者,当buffer为空(c3)和消费过程结束时(c6)进入睡眠状态,所以在c3和c6处设置睡眠,-l
2表示生产总数为2

运行结果如下:
在这里插入图片描述
可以看到最后三个进程都进入了睡眠状态,程序的运行发生了错误。

第十题

在这里插入图片描述
首先,查看main-two-cvs-if.c的代码:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <string.h>

#include "common.h"
#include "common_threads.h"
#include "pc-header.h"

pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;

#include "main-header.h"

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;
	if (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;
	if (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);
}

// must set these appropriately to use "main-common.c"
pthread_cond_t *fill_cv = &fill;
pthread_cond_t *empty_cv = &empty;

// all codes use this common base to start producers/consumers
// and all the other related stuff
#include "main-common.c"


经过,仔细查看我们可以知道这里的if语句是有问题。就是当一个一个生产者和两个消费者,生产者生产完之后,第一个消费者运行到c4,这时候发生了中断,另一个进程同样也运行到了c4,这时候其中有一个进程继续运行将缓冲区清空,之后另一个进程进行运行的时候,出现了断言为假,程序的运行就出现了错误。
运行指令:./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中的数据消费之后,另一个消费者再次尝试获取数据,将会发现无数据可取,导致断言触发。

第十一题

在这里插入图片描述

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <string.h>

#include "common.h"
#include "common_threads.h"

#include "pc-header.h"

pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;

#include "main-header.h"

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;
	}
	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++;
    }

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

// must set these appropriately to use "main-common.c"
pthread_cond_t *fill_cv = &fill;
pthread_cond_t *empty_cv = &empty;

// all codes use this common base to start producers/consumers
// and all the other related stuff
#include "main-common.c"

这个代码的问题与上面第10题类似,再put()或get()就把锁打开,可能导致别的线程插入进来运行,导致该线程再次get()的时候结果无数据可取,导致断言触发!
根据以上分析,我们可以构造一个让该问题突出的睡眠序列:
./main-two-cvs-while-extra-unlock -p 1 -P 0,0,1,1,0,0,1 -c 2 -m 1 -C 0,0,1,1,0,0,1:0,0,1,1,0,0,1 -l 4 -v,运行结果如下:
在这里插入图片描述
当第一个进程要运行get的时候,另一个进程插入执行get函数,导致第一个进程发生断言错误。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值