# 斯坦福编程范式 CS107_18

# 上节课提到的冰淇淋商店模拟问题

我们有一个收银员;十个顾客,会随机买 1 到 4 个冰淇淋;制作冰淇淋的店员,10 到 40 个,每个店员一次只做一个甜筒; 只有一个经理,店员需要将冰淇淋交给经理检查,并且一次只有一个店员进去检查。所以一共有四种线程,一共大概可能 52 个线程。 我们需要一些方式使他们之间能够相互通讯。

显然的店员进办公室需要一把锁,因为一次只能有一个店员进办公室。经理一直都在办公室中等待,直到检查的冰淇淋数量达到一天的要求,所以经理是一个循环。当顾客拿到他们订的冰淇淋后,他们就排队去收银员那里,并且这个队列应该是 FIFO 形式的,先进先出。

# main 函数

main 函数主要是产生我们需要的所有线程,当然也不是所有线程都在 main 函数中生成,比如,顾客负责生成店员的线程,即,只有顾客要求了店员才会去生产冰淇淋。

int main(-,-){
    int totalCones = 0;	// 甜筒的数量
    InitThreadPackage();	// 初始化线程
    SehpSemaphores();		//
    for(int i = 0;i<10;i++){
        int numCones = RandomInteger(1,4);
        ThreadNew(Name,Customer,1,numCones); // 顾客线程
        totolCones += numCones;
    }
    ThreadNew(Name2,Cashier,0);	// 收银员线程
    ThreadNew(Name3,Manager,1,totalCones); // 经理线程
    RunAllThreads(); // 开启所有的线程
    SemaphoreFree();
    return 0;
}

# 经理

下面从经理开始讲起,因为经理较为简单。我们需要知道,经理检查完甜筒后,甜筒是否合格,这就涉及到一些通讯方式。

下面通过使用一个全局的结构体进行检查这一个动作。bool 类型的 passed 表示甜筒是否通过;信号量 requested 表示经理的锁初始化为 0,经理要等待有一个人进来,否则他就一直处于阻塞状态。信号量 finished 表示店员的冰淇淋是否检查完毕,如果检查完了就唤醒店员。

struct inspection{
    bool passed;
    Semaphore requested(0);
    Semaphore finished(0);
}

下面我们来完成经理函数, numApproved 是今天检查的甜筒总数, numInspected 是检查过的店员的数量。当检查的甜筒总数没有达到目标值时,就要一直等待店员提供冰淇淋进行检查,并且检查是否通过是一个随机的。如果通过检查了,就 numApproved 加 1,并且结束的时候通过信号量告知店员检查结束了。

void Manager(int totalConesNeeded){
    int numApproved = 0;
    int numInspected = 0;
    while(numApproved<totalNumNeeded){
        SemaphoreWait(inspection.requested);
        numInspected++;
        inspection.passed = RandomChance(0,1);
        if(inspection.passed)
            numApproved++;
        SemaphoreSignal(inspection.finished);
    }
}

# 店员

被顾客告知需要做甜筒后,店员需要做甜筒并且让经理来检查,一旦通过检查,就将甜筒交给顾客,否则就继续做甜筒。于是我们来更新一下我们存放信号量的结构体。添加一个信号量 lock 并初始化为 1,使得至少初始能有一个店员进入经理办公室。

struct inspection{
    bool passed;
    Semaphore requested(0);
    Semaphore finished(0);
    Semaphore lock(1);
}

店员在不通过的时候一直做甜筒,做完就等待进入办公室,并在进入办公室的时候唤醒经理,并等待经理检查完毕。取得是否通过的结果后,店员释放锁。并最终通知顾客甜筒好了。

** 问题:** 为什么需要 requested 那个锁?因为如果不这样的话,经理就会对进来的人持续给出评价,并且你也不确定哪一个评价是你这个甜筒的。

void Clerk(Semaphore semaToSignal){
    bool passed = flase;
    while(!passed){
        MakeCone();
        SemaphoreWait(inspection.lock);
        SenaphoreSignal(inspection.requested);
        SemaphoreWait(inspection.finished);
        passed = inspection.passed;
        SemaphoreSignal(inspection.lock);
    }
    SemaphoreSignal(semaToSignal);
}

# 顾客

一开始顾客什么也不做,随后他需要找 n 个店员来做他这 n 个甜筒。信号量 clerksDone 是等待店员做好了甜筒通知你并把甜筒给你。接着是等待店员把所有的甜筒做完,并释放店员线程。之后进行排队。

随后去去一个号码,并且不要和其他人同时去拿。再等待排到你的时候你会将收银员唤醒,并最终等待你的位置的账单被处理完。

void Costomer(int numCones){
    BrowX();
    Semaphore clerksDone(0);
    for(int i = 0;i < numCones;i++){
        ThreadNew(Name,Clerk,1,clerksDone);
    }
    for(int i = 0;i<numCones;i++)
        SemaphoreWait(clerksDone);
    SemaphoreFree(clerksDone);
    WalkToCashier();
    
    SemaphoreWait(line.lock);
    int place = line.number++;
    SemaphoreSignal(line.lock)
    SemaphoreSignal(line.requested);
    SemaphoreWait(line.customers[place]);
}

# 收银员

接下来看我们的其他结构体,这个结构体能够处理顾客和收银员之间的通讯的所有信号量。现在顾客必须去收银员那里,并必须排队等待轮到他的时候,所以他需要获得一个数字,也就是队列中的下一个可占用的位置。

number 表示顾客当前所在队列的号,信号量 requested 表示收银员当前是否被需要,信号量 customers 是一个数组,当收银员处理完对应队列中某一个人的帐单后,就告知此人可以走了。并且由于 number 是共享资源,所以我们也需要对它加上一个锁。

struct line{
    int number;	//0
    Semaphore requested; //0
    Semaphore customers[10];
    Semaphore lock; //1
}

对于 10 个顾客,收银员需要知道自己是否被需要了,

void Cashier(){
    for(int i = 0; i< 10; i++){
        SemaphoreWait(line.requested);
        Checkout(i);
        SemaphoreSignal(line.customers[i]);
    }
}