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。