实现同步,临界区问题,锁和信号量的实现

Locks and Context Switching


Concurrency并发

多个任务在同一时间段内进行,但不一定是真的同时运行。例如,在单核CPU上,操作系统通过快速切换任务实现并发。

补充:并行 ,多个任务真正地同时运行,通常需要多核CPU支持,每个核心同时运行一个任务。

Amdahl's law

通过并行加快速率。

S=1/((1-P)+P/N)

P:可以被并行化的程序部分的比例

N:并行化使用的处理器数量或执行线程数量

For example:

If 95% of program can be parallelized (processor=4096), theoretical maximum speedup is x20

Synchronization同步

确保并发的进程或者线程不会同时执行某些特定的程序(Critical Section)

同步的两种方法:Competition 和 Coordination

Competition

Competing for a variable that two processes want to read or update simutaneously.

临界区:Code section that accesses a shared resource

解决方案就是实现互斥:At a given time, only one process could run within the critical section.

那么如何实现互斥呢?(Buliding Critical Section)

Lock

Semaphores

Monitor

Message

产生竞争需要的race condition

  • An error (e.g. a lost update) that occursdue to multiple processes 'racing' in an uncontrolled manner through asection of non-atomic code

  • A race condition occurs when output isdependent on the timing or sequence of uncontrolled events

  • Arises if multiple executing threadsenter a code critical sections around the same time, and both attempt to updateshared data.

典例 银行存钱

(LOCK实现):

关键点:创建bank类的update方法public void synchronized update(){ }

java 复制代码
public class Bank_account   
     { 
       private int bal = 0;  
       public Bank_account(int start_balance) 
          { bal = start_balance; } 
       public void synchronized update(int amount)  
            { bal = bal + amount; }
      } 

public class DepositTask implements Runnable{
    private Bank_account account;
    private int amount; 
    //构造方法
    public Mythread(Bank_account account,int amount){
       this.account=account;
       this.amount=amount;
    }
    public void run(){
       account.updata(amount);
    }
}

public class main{
    public static void main(String args[]){
    Bank_account bank=new Bank_account(100);
    DepositTask task1=new DepositTask(bank,5);
    DepositTask task2=new DepositTask(bank,5);
    Thread t1=new Thread(task1);
    Thread t2=new Thread(task2);
    t1.start;
    t2.start;
    thread1.join();
    thread2.join();
}
}
Coordination

Coordination: a process want to another process that a needed result is accessible.

典例:生产者消费者问题(老板员工箱子)

Context switch 上下文切换

Execution jumps to another part of memory.

一个线程的上下文包括:程序或者函数执行的位置+栈中函数调用的顺序的位置

  • Place in the program (or function)that the thread is executing 程序或者函数执行的位置

  • Place in stack that remembers the sequence of function calls the thread is making as it executes program (each thread needs own stack)

C implement :Program counter (PC )+Stack Pointer(SP)

上下文切换的三步骤:

  • De-schedulecurrently-running thread

    --Save PC and SP CPU registers of currentrunning thread

    --Requiredso that thread resumes execution exactly where left off

  • Scheduler selects 'best' ready thread to run next
    --Time-slicing , priority , starvation
    --Hardware architecture, etc.

  • Restoreregister contents back to PC & SP registers

    --Thread resumes where it last left off (PC is loaded last)

Semaphores 信号量

上部分内容说到,实现同步可以竞争,而竞争就是要处理好临界区(Critical Section: code segments that accesses the shared resources)的互斥(At a given time, only one process can run with the critical section ). 实现互斥有四种方式:Lock,semaphores, monitor, message. 上部分内容运用Lock实现了银行存钱问题(主要运用synchronized),接下来我们讨论运用semaphores实现互斥。

实现信号量的重要组成三部分:

Counter(表示可用资源),

wait()/P():将Counter减1,如果counter为负值表示有一些进程在等待,那么block刚想要进入的进程

signal()/V(): 将Counter加1(表示释放了一个资源/进去了一个进程/队列中少了一个进程)。

典例:银行存钱问题,运用信号量

java 复制代码
public class Bank_account{
    private int bal=0;
    private Semaphone mutex =1;//运用信号量实现互斥only one process run within critical section
    public void Bank_account(int balance){
       bal=balance;
    }
    public void updata(int amount){
       mutex.wait();
       bal=bal+amount;
       mutex.signal();
    }
}

实际上java已经帮我们实现了semaphone类的创建,我们只需要导入import java.util.concurrent.Semaphore;就可以直接new一个信号量用:

创建信号量-->创建一个runnable实例放入信号量作为参数--->创建一个线程实例放入runnable实例

java 复制代码
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        // 创建一个信号量,最多允许1个线程同时访问
        Semaphore semaphore = new Semaphore(1);

        // 创建多个线程访问同一资源
        for (int i = 1; i <= 5; i++) {
            Thread thread = new Thread(new Task(semaphore), "Thread-" + i);
            thread.start();
        }
    }
}

// 定义任务类
class Task implements Runnable {
    private Semaphore semaphore;

    public Task(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " is waiting for a permit...");
            semaphore.acquire();  // 获取许可
            System.out.println(Thread.currentThread().getName() + " got a permit!");

            // 模拟资源访问
            Thread.sleep(2000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " is releasing the permit...");
            semaphore.release();  // 释放许可
        }
    }
}

java中的semaphore

接下来我们看一下semaphore 在java中是怎样实现的:

创建counter+创建P+创建V

java 复制代码
public class Semaphore { 
    private int count = 0; 
    public Semaphore(int init_val) { 
           count = init_val; // 调用的时候初始counter/资源数/允许访问进程数量 
    } 
    public synchronized void P() { 
           count = count - 1; 
           while (count < 0) wait(); //why not 'if'?  to prevent Spurious wakeup 虚假唤醒
       } 
    public synchronized void V() { 
            count = count + 1; /* if there is one, wake a waiter; */
            if (count <= 0) notifyAll(); /*why not use 'notify()'? */ 
    } 
}

我们需要注意以下几点:

  • 在P中,为什么count<0 需要wait?因为count小于0代表队列中有等待,前面的进程在等待,自然你也需要等待。若是counter等于或者大于0,说明前面没有人,你直接进去就行,没必要等。

  • 在V中,为什么判断count<=0才notifyAll?同理说明队列中有等待,所以需要用notify,如果队列中无等待,那么直接进来也不需要notify

  • while (count < 0) wait(); why not 'if'? 防止虚假唤醒+代码中用了notifyAll

  • 1,To prevent spurious wake, 2, we use notifyAll() in P(), 多线程竞争的情况下,while 能确保只有满足条件的线程才能继续执行,其余线程会重新等待。

  • notifyAll() why not notify? notify概念+notifyAll概念+notfiy坏处(不满足条件的被唤醒再次进入等待状态,满足条件的无法唤醒+某些线程starvation)

  • 1,notify()Wake up a randomly waiting thread. 2, notifyAll is to wakeup all the waiting threads. 3, when we use notify(),it could wakeup a thread that is not ready, while the satisfied thread could not be woken up. Using notify() could cause some threads starvation.

虚假唤醒Spurious wakeup

虚假唤醒 是指线程在未满足条件、也未接收到明确通知(如 notify()notifyAll())的情况下,进程被意外唤醒的现象。

运用信号量实现有序性(生产者消费者问题)

可用资源最初为0--->生产者产生可用资源(siganl())--->消费者消费资源(wait())

以老板员工制作箱子为例:最开始箱子空要求-->老板定义箱子尺寸--->员工得到尺寸做箱子

java 复制代码
public class BoxDimension{
   private int dim = 0;
   private Semaphore sem = 0;
   public void put(int d) 
      {dim = d; 
      sem.signal(); //sem+1 ; sem.V()
      } 
    public int get() 
      {  sem.wait();//sem-1; sem.P()
         return dim; }
} 

注意:一开始Semaphore sem =0,表示没有资源;老板先定义尺寸然后标记可用资源+1和通知员工;员工先看看能不能访问资源然后再return dim。