操作系统学习笔记——信号量

通过锁可以实现互斥的访问,但是光有互斥不够,还需要同步的机制,甚至在临界区需要多个线程进入临界区执行,所以需要更高层的同步互斥语义。

信号量抽象数据类型

       一个整型(sem),两个原子操作

       P():sem减 1 ,如果sem <0 ,等待,否则继续; V():sem 加1,如果sem<=0,唤醒一个等待的P

信号量的使用

信号量特点:

信号量是整数;信号量是被保护的变量:初始化完成后,唯一改变一个信号量的值的办法是通过P()和V(),操作必须是原子;

P()能够阻塞,V()不会阻塞;使用FIFO

两种类型信号量:二进制信号量,可以是0或1;一般/计数信号量,可取任何非负值。两者相互表现,给定一个可以实现另一个。

信号量可以用在2个方面:互斥,条件同步(调度约束——一个线程等待另一个线程的事情发生)

用二进制信号量实现的互斥:模拟LOCK操作。完全可以代替Lock

mutex = new Semaphore(1);
mutex->P();
...
Critical Section;
...
mutex->V();

用二进制信号量实现的调度约束:

condition = new Semaphore(0);

线程A:

...
condition->P();
...


线程B:




...
condition->V();
...

P()等待,V()发出信号。B唤醒A

条件同步的问题,二进制信号量就无法完成了,所以需要计数信号量。

一个线程等待另一个线程处理事情:比如生产东西或消费东西;互斥(锁机制)是不够的。

例:有界缓冲区的 生产者——消费者问题

一个或多个生产者产生数据将数据放在一个缓冲区里;单个消费者每次从缓冲区取出数据;在任何一个时间只有一个生产者或消费者可以访问该缓冲区。

Producer -> Buffer  -> Consumer

正确性要求:

      在任何一个时间只能有一个线程操作缓冲区(互斥);当缓冲区为空,消费者必须等待生产者(调度/同步约束);当缓冲区满,生产者必须等待消费者(调度/同步约束)

每个约束用一个单独的信号量:

       二进制信号量互斥;一般信号量fullBuffers;一般信号量emptyBuffers.

赋初值:

Class BoundedBuffer{
    mutex = new Semaphore(1);
    fullBuffers = new Semaphore(0);
    emptyBuffers = new Semaphore(n);
}

操作:

//生产
BoundedBuffer::Deposit(c){
    emptyBuffers->P();
    mutex->P();//确保互斥性
    Add c to the buffer;
    mutex->V();
    fullBuffers->V();
}

//消费
BoundedBuffer::Remove(c){
    fullBuffers->P();
    mutex->P();
    Remove c from buffer;
    mutex->V();
    emptyBuffers->V();
}

P,V操作的顺序有影响吗?

      V操作交换顺序没问题。P操作交换顺序有问题,因为P会阻塞,假如BUFFER是满的,先执行Mutex后,emptyBuffers这一步就阻塞,消费者那儿也执行不下去,会导致死锁。

所以要设置好P,V顺序,还要设置好初始值。

信号量的实现

class Semaphore{
    int sem;
    WaitQueue q;
}

Semaphore::P(){
    sem--;
    if(sem<0){
        Add this thread t to q;
        block(p);
    }
}

Semaphore::V(){
    sem++;
    if(sem<=0){
        Remove a thread t from q;
        wakeup(t);
    }
}

信号量的双用途:互斥和条件同步;但等待条件是独立的互斥。

不能够处理死锁问题。

猜你喜欢

转载自blog.csdn.net/qq_22080999/article/details/82314332