Operating system notes--semaphore, monitor and synchronization

1--Signal amount

1-1--Basic knowledge

        Semaphores can be used to implement mutual exclusion and synchronization between threads, and their abstract data types include: an integer data (Sem) and two atomic operations (P() and V());

        P() operation: decrement Sem by 1, if Sem < 0, the thread executing P() operation will wait, otherwise continue to execute;

        V() operation: add 1 to Sem, if Sem <= 0, the thread executing V() ​​operation will wake up a waiting thread;

Basic concept of semaphore:

       ① The semaphore is an integer and also a protected variable, the value of the semaphore can only be changed through the P() and V() atomic operations;

       ② The P() operation will block, but the V() operation will not block;

       ③ The semaphore is fair, and the V() operation to wake up the thread is also fair. Generally, the FIFO (first in, first out) algorithm is used to wake up the thread in the waiting queue; 

        ④ There are two types of semaphores: the first is a binary semaphore, represented by 0 and 1; the second is a counting semaphore, which can be represented by any non-negative value;

        ⑤ The purpose of the semaphore: to achieve mutual exclusion and conditional synchronization between threads;

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

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--Basic example

An example of using semaphores to synchronize mutual exclusion between threads: the problem of producers and consumers of bounded buffers

        ① One or more producers generate data and store the data in a buffer;

        ② A single consumer fetches data from the buffer each time;

        ③ Only one producer or consumer can access the buffer at any one time;

The above example abstracts three requirements:

        ① Only one thread can operate the buffer at any one time (mutual exclusion);

        ② When the buffer is empty, the consumer must wait for the producer (scheduling/synchronization constraints);

        ③ When the buffer is full, the producer must wait for the consumer (scheduling/synchronization mutual exclusion);

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--Semaphore implementation

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--Monitor

2-1--Basic knowledge

        The semaphore mechanism has problems such as difficulty in programming and error-prone; in order to better realize synchronous mutual exclusion, an abstract concept called a monitor is introduced;

        The monitor can be abstracted as a structure consisting of a lock and 0 or more condition variables. The lock is used to specify the critical area (only one thread is allowed to enter the area managed by the monitor at a time), and the condition variable is used to control the waiting and wake;

Lock:

        Lock::Acquire(): Wait until the lock is available, and then seize the lock;

        Lock::Release(): Release the lock and wake up the waiting thread;

Condition Variable:

        Wait(): release lock, thread sleep

        Signal(): Wake up the waiting thread

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--Basic example

        Problems implementing producers and consumers using monitors:

// 初始化
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--Classic synchronization problem

Guess you like

Origin blog.csdn.net/weixin_43863869/article/details/130724356