现代操作系统:进程与线程(十)

2.4.4 Sleep and Weakup睡眠与唤醒

注意:Tanenbaum提出了忙碌等待(如上所述)和阻塞(进程切换)两种解决方案。我们只研究忙碌等待的解决方案,它更容易实现阻塞解决方案。Sleep和Wakeup是最简单的阻塞原语。休眠自动阻塞进程,而唤醒则解除阻塞休眠进程。然而,睡眠和唤醒是如何实现的还远远不清楚。实际上,在内心深处,他们通常使用TAS或类似的原语。我们将不讨论这些解决方案。

Homework 13

家庭作业:解释繁忙等待和阻塞进程等待之间的区别。

区别:繁忙等待需要消耗CPU资源而阻塞等待无需消耗CPU资源。所有的繁忙等待都有一个while循环以实现阻塞的效果,实际上CPU在不断计算这个while条件是否成立,而阻塞等待已经将进程挂起。

Busy Waiting的问题:如果一台计算机有两个进程,H优先级高,L优先级低。调度规则规定,H处于就绪态就可以直接运行。但是在某一时刻,L处于临界区中,H变为就绪态,准备运行(一条I/O操作结束)。此时H处于Busy Waiting状态,但是由于当H位于就绪态时L并不会被调度,因此L永远也无法离开临界区,所以H将一直处于Busy Waiting状态,这种情况称为优先级反转(Priority Inversion Problem)。

 

2.4.5 Semaphores信号量

术语说明:Tanenbaum只在阻塞解决方案中使用信号量这个术语。我将使用这个术语来表示我们的忙碌等待解决方案(以及阻塞解决方案,我们没有涉及到)。其他人把我们忙碌等待的解决方案称为自旋锁。

 

P and V   P操作和V操作

入口代码通常称为P,出口代码称为V。因此,关键部分的问题是写出P和V,以便右边的循环满足左边的条件。P操作信号量-1,V操作信号量+1。

loop forever

     P

     critical-section

     V

     non-critical-section

  • 互斥;
  • 没有速度的假设;
  • 在NCS(非临界区代码块)中没有被进程阻塞;
  • 向前推进过程;

We have just seen a solution to the critical section problem, namely:

    P   is   while (TAS(s)) {}

    V   is   s<--false

 

Binary Semaphores二值信号量(0-1信号量)

二值信号量是TAS指令的一个抽象,类似于TAS中的那个bool变量,二值信号量能表达的信号就是0-close,1-open。

支持P V操作,如下:

while (S==closed) {}

S←closed

其中测试S=open和设置S←Close是单个原子操作,不可分割。

  • 非正式的,进程会等待S变为Open,然后跳过轮询设置其为Close;
  • 换一种说法,两个同时进行P(S)的进程不可能同时看到S=open;
  • 出了临界区记得V (S)把门打开;

上面的代码不是真实的,也就是说,它不是p的真实实现。它需要一个由两条指令组成的原子序列,毕竟,这就是我们首先要实现的,相反,上面的代码是P要具有的效果的定义。

loop forever

     P(S)

     CS

     V(S)

     NCS

重复一下:对于任意数量的进程,可以使用P和V来解决临界区问题,如图所示。

对于任意数量的进程,我们看到的唯一解决方案是2.3.4之前的那个,通过test和set实现P(S)。注意:Peterson的软件解决方案要求每个进程知道它的进程号,TAS解决方案没有。而且,P和V的定义不允许使用过程号。因此,严格地说,Peterson并没有提供P和V的实现,但是,他确实解决了临界区问题。

 

(The Need for) Counting (or Generalized) Semaphores通用信号量

为了解决其他的协调问题,我们希望对二元信号量进程拓展。

  • 对于二元信号量,两个连续的v不允许两个后续的p成功(门不能被双开)。就是即便有两个连续的V操作,但是它的最多也就是1,所以再来一个P就变成0了。
    • 我们可能希望将临界区中的进程数量限制在3或4个,而不是总是1个。

上述两个(相关的)缺点都可以通过不局限于二进制变量来克服,而是基于非负整数定义一个广义或计数信号量,就相当于把Bool改成Int型。

 

Intuition for (Binary and Counting) Semaphores二元信号量和计数信号量的感知

        设想一个基于TAS的二元信号量S,如果当前S为False则信号量被形容成一扇打开的门,任何进程都可以通过这个门;如果S是True,则表示大门关闭。有一个助理通过检查门的状态让进程进入或者在门外等待,如果门是开着的,那么助理会让一个进程进门然后把门锁死,当这个进程从门中出来后,助理会把门打开让下一个进程进入。

        而一个计数信号量的形容就是,一扇内外开关的门变成一个旋转门,只允许一定数量的进程通过,直到另一个进程允许增加通过的进程数量。

  • 计数信号量S是一个非负整数值;
  • 支持PV操作;
  • P操作表示为:

while(S==0){}

S--;

        当 时可P且S的减小是原子性的!

  • 也就是说,当门只要是打开的就可以允许对应数量的进程进门;
  • 原子性的另一种说法是:其实说了半天就是同时只有一个进程/线程可以对信号量变量执行读写操作,悲观锁是一种处理方式,CAS也是一种方式;

initially S=k

 

loop forever

     P(S)

     SCS   -- semi-critical-section

     V(S)

     NCS

 

Counting Semaphores and Semi-critical Sections计数信号量和半临界区

计数信号量可以解决我所说的半临界区问题,在这个区段中允许最多K个进程,答案出现在右边,当k=1时,我们有原始的临界区问题。

 

Solving the Producer-Consumer Problem Using Semaphores解决生产者消费者问题

回想一下,我对信号量的定义与Tanenbaum的不同(忙等待和阻塞);因此,我对各种协调问题的解决方法与他的不同也就不足为奇了。

        与之前所有进程都相同的互斥问题不同,生产者-消费者问题有两类进程:

  • 生产者,它产生资料并将它们插入缓冲区;
  • 消费者,从缓冲区中删除项目并消费它们;

为了完成对生产者-消费者-消费者问题的定义,我们必须回答两个问题:

问:如果生产者遇到满缓冲区会发生什么?

答:生产进程阻塞,等待缓冲区变为非满的状态(有消费者消耗资料);

问:如果消费者遇到空缓冲区怎么办?

答:消费者等待缓冲区变为非空(有生产者提供资料)。

生产者-消费者问题也称为有界缓冲区问题,这个替代名称是活动实体在较低级别被数据结构替换的另一个例子(Finkel的级别原则)。

 

解决方案:

假设有一个面包架,每个面包占一个槽,设k为槽的数量(面包架中能容纳面包的数量)。声明e为一个计数信号量(表示空的槽的数量),f为一个计数信号量(表示已被使用的槽的数量)。虽然有K个进程可以同时进入临界区,但是它们如果需要对同一个变量进行操作仍然要对该变量加锁!

Initially e=k, f=0 (counting semaphores)

          b=open (binary semaphore)

 

Producer                      Consumer

 

loop forever                  loop forever

   produce-item                  P(f)

   P(e)                          P(b); take item from buf; V(b)

   P(b); add item to buf; V(b)       V(e)

   V(f)                          consume-item

假设初始时刻面包架是空的,那么初始化e=k,f=0,b=0;我们假设缓冲区本身只能串行访问。也就是说,一次只能做一个操作。这解释了P(b) V(b)周围的缓冲区操作。我使用,并将三条语句放在一行中,表示缓冲区的插入或删除被视为一个原子操作。当然,这种编写风格只是一种约定,原子性的实施是由P/V完成的。P(e),V(f) motif用于强制有界交替。如果k=1,它给出严格的交替。

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <sys/types.h>

#include <pthread.h>

#include <semaphore.h>

 

#define M 10

#define PROVIDER 3

#define CONSUMER 5

 

sem_t emptySem, fullSem;

pthread_mutex_t bufferLock;

 

int breadCounter[M] = {0};

 

void* ProviderThread(void* params)

{

    int providerID = *((int*)params);

    while (1)

    {

        sem_wait(&emptySem);

        pthread_mutex_lock(&bufferLock);

        int i = 0;

        for (i = 0; i < M; i++)

        {

            if (breadCounter[i] == 0)

            {

                breadCounter[i] = 1;

                printf("Provider No.%d put bread at No.%d\n", providerID, i);

                break;

            }

        }

        pthread_mutex_unlock(&bufferLock);

        sem_post(&fullSem);

        sleep(1);

    }

}

 

void* ConsumerThread(void* params)

{

    int consumerID = *((int*)params);

    while (1)

    {

        sem_wait(&fullSem);

        pthread_mutex_lock(&bufferLock);

        int i = 0;

        for (i = 0; i < M; i++)

        {

            if (breadCounter[i] == 1)

            {

                breadCounter[i] = 0;

                printf("Consumer No.%d take No.%d bread!\n", consumerID, i);

                break;

            }

        }

        pthread_mutex_unlock(&bufferLock);

        sem_post(&emptySem);

 

        sleep(consumerID);

    }

 

}

 

int main()

{

    sem_init(&emptySem, 0, M);

    sem_init(&fullSem, 0, 0);

    pthread_mutex_init(&bufferLock, NULL);

 

    pthread_t pthreadList[PROVIDER + CONSUMER];

    int* providerIDList = (int*)malloc(sizeof(int) * PROVIDER);

    int* consumerIDList = (int*)malloc(sizeof(int) * CONSUMER);

 

    int i = 0;

    for (i = 0; i < PROVIDER; i++)

    {

        providerIDList[i] = i + 1;

        pthread_create(&pthreadList[i], NULL, ProviderThread, (void*)&providerIDList[i]);

    }

 

    for (i = 0; i < CONSUMER; i++)

    {

        consumerIDList[i] = i + 1;

        pthread_create(&pthreadList[PROVIDER + i], NULL, ConsumerThread, (void*)&consumerIDList[i]);

    }

 

    for (i = 0; i < PROVIDER + CONSUMER; i++)

    {

        pthread_join(pthreadList[i], NULL);

    }

 

    free(providerIDList);

    free(consumerIDList);

 

    return 0;

}

 

2.4.6 Mutex互斥锁

注意:我们用信号量这个术语来表示二进制信号量,并且明确地说正整数版本的广义或计数信号量,Tanenbaum用信号量表示正整数解,用互斥量表示二进制版本。同样,如上所述,Tanenbaum信号量/互斥量意味着阻塞实现;而我在忙碌等待和阻塞实现中都使用二进制/计数信号量。最后,请记住,在本课程中,我们唯一的解决方案是忙碌等待。

下面是一个罗塞塔石碑,用来翻译Tanenbaum和mine的术语。

My Terminology

 

 

Busy wait

block/switch

 

critical

(binary) semaphore

(binary) semaphore

 

semi-critical

counting semaphore

counting semaphore

 

Tanenbaum's Terminology

 

Busy wait

block/switch

critical

enter/leave region

mutex

semi-critical

no name

semaphore

posted on 2022-01-06 15:03  ThomasZhong  阅读(83)  评论(0编辑  收藏  举报

导航