操作系统笔记--信号量、管程与同步

1--信号量

1-1--基本知识

        信号量可用于实现线程间的互斥与同步,其抽象数据类型包括:一个整型数据(Sem)和两个原子操作(P() 和 V());

        P() 操作:将 Sem 减1,如果 Sem < 0,则执行 P() 操作的线程会等待,否则继续执行;

        V() 操作:将 Sem 加1,如果 Sem <= 0,则执行 V() 操作的线程会唤醒一个等待的线程;

信号量基本概念:

       ① 信号量是一个整数,也是一个被保护的变量,只能通过 P() 和 V() 原子操作来改变信号量的值;

       ② P() 操作会阻塞,V() 操作不会阻塞;

       ③ 信号量是公平的,V() 操作唤醒线程也是公平的,一般采取 FIFO(先进先出)算法来唤醒等待队列中的线程; 

        ④ 信号量有两种类型:第一是二进制信号量,用0和1表示;第二是计数信号量,可用任何非负值表示;

        ⑤ 信号量的用途:实现线程间的互斥和条件同步;

// 二进制实现线程间的互斥

mutex = new Semaphore(1); // 初始化为1

mutex->P(); // P操作,减1,判断是等待还是继续执行
...
Critical Section; // 临界区
...
mutex->V(); // V操作,加1,是否唤醒一个等待的线程
// 二进制信号量实现同步调度

condition = new Semaphore(0); // 初始化为0

// 线程A
...
condition->P(); // 由于condition初始值为0,执行P()操作变成负数,线程A处于等待状态
    ...

// 线程B
...
condition->V(); // 执行V()操作,唤醒处于等待状态的线程A
    ...

// P操作使线程A等待,直到线程B执行V操作来唤醒调度线程A

1-2--基本实例

使用信号量实现线程间同步互斥的实例:有界缓冲区的生产者和消费者问题

        ① 一个或多个生产者产生数据并将数据存放在一个缓冲区内;

        ② 单个消费者每次从缓冲区中取出数据;

        ③ 在任何一个时间里只有一个生产者或消费者可以访问缓冲区;

上述实例抽象三个出三个要求:

        ① 在任何一个时间只能有一个线程操作缓冲区(互斥);

        ② 当缓冲区为空时,消费者必须等待生产者(调度/同步约束);

        ③ 当缓冲区为满时,生产者必须等待消费者(调度/同步互斥);

Class BoundedBuffer{
    mutex = new Semaphore(1); // 互斥,初始为1
    fullBuffers = new Semaphore(0); // 同步,初始为0,可理解为buffer当前存量
    emptyBuffers = new Semaphore(n); // 同步,初始为n,可理解为buffer剩余容量
}

// 生产者
BoundedBuffer::Deposit(c){
    emptyBuffer->P(); // 剩余容量减1,当为负值时,说明buffer已满,阻塞所有生产线程,直到生产线程唤醒
    mutex->P(); // 互斥锁,只允许一个线程进行临界区
    Add c to the buffer;
    mutex->V(); // 退出临界区,解锁
    fullBuffer->V(); // 当前存量加1
}

// 消费者
BoundedBuffer::Remove(c){
    fullBuffers->P(); // 当前存量假1,当为负值时,说明buffer已空,阻塞所有消费线程,直到生产线程唤醒
    mutex->P(); // 互斥锁,只允许一个线程进行临界区
    Remove c from buffer;
    mutex->V(); //退出临界区,解锁
    emptyBuffers->V(); // 剩余容量加1
}

1-3--信号量实现

classSemaphore{
    int sem;
    WaitQueue q;
}

Semaphore::P(){
    sem--;
    if(sem < 0){
        Add this thread t to q; // 将线程t置于等待队列q
        block(p); // 睡眠
    }
}

Semaphore::V(){
    sem++;
    if(sem <= 0){
        Remove a thread t from q; // 将线程t从等待队列q中移除
        wakeup(t); // 唤醒等待队列q中的线程t
    }
}

2--管程

2-1--基本知识

        信号量机制存在编写程序困难、容易出错等问题;为了更好地实现同步互斥,引入一个抽象的概念称为管程;

        管程可抽象为由一个锁和 0 个或多个条件变量构成的结构,锁用于指定临界区(每次只允许一个线程进入管程管理的区域),条件变量用于控制线程的等待和唤醒;

Lock:

        Lock::Acquire():一直等待直到锁可用,然后抢占锁;

        Lock::Release():释放锁,唤醒等待的线程;

Condition Variable:

        Wait():释放锁,线程睡眠

        Signal():唤醒等待的线程

Class Condition{
    int numWaiting = 0; // 等待的线程数
    WaitQueue Q; // 等待队列
}

Condition::Wait(lock){
    numWaiting++; // 等待的线程数加1
    Add this thread t to q; // 将线程t置于等待队列中
    release(lock); // 释放锁
    schedule(); // 选择下一个线程执行
    require(lock); // 获取锁
}

// 等待操作先释放锁的原因:
// 管程有很多入口,但每次只能开放其中一个入口,只能让一个进程或线程进入管程管理的区域
// 当线程使用wait()条件变量时,因为自身处在等待状态,这时需要释放管程的使用权,也就是释放管程的入口来让其他线程进入,因此要释放锁

Condition::Signal(){
    if(numWaiting > 0){ // 表明当前有线程正在等待
        Remove a thread t from q; // 从等待队列中移除一个等待的线程t
        wakeup(t); // 唤醒线程t
        numWaiting--; // 等待线程数减1
    }
}

2-2--基本实例

        利用管程实现生产者和消费者的问题:

// 初始化
classBoundedBuffer{
    Lock lock; // 锁
    int count = 0; // 当前容量
    // notFull条件变量管理生产线程
    // notEmpty条件变量管理消费线程
    Condition notFull, notEmpty;
}

// 生产者
BoundedBuffer::Deposit(c){
    lock->Acquire(); // 实现互斥,因为管程每次只允许一个线程或进程通过
    while(count == n) // 容量已满,线程循环等待
        notFull.Wait(&lock);
    Add c to the buffer;
    count++;
    notEmpty.Signal(); // 唤醒等待的线程
    lock->Release();
}

// 消费者
BoundedBuffer::Remove(c){
    lock->Acquire(); // 实现互斥,因为管程每次只允许一个线程或进程通过
    while(count == 0) // 容量为空,线程循环等待
        notEmpty.Wait(&lock);
    Remove c from buffer;
    count--;
    notFull.Signal(); // 唤醒等待的线程
    lock->Release();
}

3--经典同步问题

3-1--读者和写者问题

读者和写者问题面向对共享数据的访问,其包含两种类型的使用者:

        ① 读者:只读取数据,不需要修改数据;

        ② 写者:读取和修改数据;

读者和写者问题的约束:

        ① 允许同一时间有多个读者,但在任何时候只有一个写者;

        ② 当没有写者时,读者才能访问数据;

        ③ 当没有读者和写者时,写者才能访问数据;

        ④ 在任何时候只能有一个线程可以操作共享变量;

3-1-1--使用信号量实现读者优先

初始化信号量:

        信号量 CountMutex 初始化为 1; 

        信号量 WriteMutex 初始化为 1;

        整数 Rcount 初始化为 0;

// 对于写者
sem_wait(WriteMutex); // 类似于P操作,将WriteMutex减1,当<0时处于等待状态

    write;

Sem_post(WriteMutex); // 类似于V操作,将WriteMutex加1,当<=0时唤醒一个等待的线程

// 对于读者
sem_wait(CountMutex); // 执行P操作,将CountMutex减1,确保临界区只有一个读线程
    if(Rcount == 0)
        sem_wait(WriteMutex); // 确保读者优先,尽管当前读者为0,也要阻塞写者线程
    ++Rcount; // 读线程数加1
sem_post(CountMutex); // 执行V操作,释放临界区的使用,允许多个读线程抢占临界区

read;

sem_wait(CountMutex); // 执行P操作,确保以下区域任何时候只有一个读者线程
    --Rount;
    if(Rcount == 0) // 没有读者线程进入读取数据的临界区,才去释放锁,唤醒写者线程
        sem_post(WriteMutex); // 执行V操作
sem_post(CountMutex) // 执行V操作

3-1-2--使用管程实现写者优先

使用管程实现写者优先,初始化以下变量:

        ① AR = 0; // 当前活跃的读者线程数

        ② AW = 0; // 当前活跃的写者线程数

        ③ WR = 0; // 当前等待的读者线程数

        ④ WW = 0; // 当前等待的写者线程数

        ⑤ Condition okToRead; // 条件变量

        ⑥ Condition okToWrite; // 条件变量

        ⑦ Lock lock; // 锁

// 对于读者
Public Database::Read(){
    //Wait until no writers;
    StartRead();
    read database;
    //Check out -- wake up waiting writers;
    DoneRead();
}

Private Database::StartRead(){
    lock.Acquire(); // 确保只有一个线程可以进入管程
    while((AW+WW) > 0){ // 确保写者优先
        WR++;
        okToRead.wait(&lock);
        WR--;
    }
    AR++;
    lock.Release();
}

Private Database::DoneRead(){
    lock.Acquire();
    AR--;
    if(AR == 0 && WW > 0){ //没有活跃的读者线程,去唤醒等待中的写者线程
        okToWrite.signal();
    }
    lock.Release();
}
// 对于写者
Public Database::Write(){
    //Wait until no reader/writers;
    StartWrite();
    write database;
    //check out -- wake up waiting readers/writes;
    Done Write();
}

Private Database::StartWrite(){
    lock.Acquire();
        while((AW+AR)>0){ // 当前没有活跃的写者和读者
            WW++;
            okToWrite.wait(&lock);
            WW--;
        }    
        AW++;
    lock.Release();
}

Private Database::DoneWrite(){
    lock.Acquire();
        AW--;
        if(WW>0){ // 唤醒等待的写者线程
            okToWrite.signal();
        }
        else if(WR > 0){ // 如果没有等待的写者线程,则广播给所有等待中的读者线程,允许其读取共享数据
            okToRead.broadcast();
        }
    lock.Release();
}

3-2--哲学家就餐问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>