第一题
首先,查看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 = ∅
// 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 = ∅
// 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 = ∅
// 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函数,导致第一个进程发生断言错误。