面向多片多核体系结构的操作系统线程调度已成为现代操作系统的5大热点问题之一,线程调度是现代操作系统的核心功能,它完成逻辑计算实体(进程/线程)到物理计算单元(CPU/核心/物理线程)的分配与调度,采用时分和空分等多种形式,实现逻辑计算实体对物理计算资源的高效共享使用。
操作系统调度器可以划分为3个主要部分:
>排队子程序
>调度分配子程序
>上下文切换子程序
其中上下文切换子程序主要完成线程运行现场相关寄存器的恢复和处理以及线程的实际切换。
举例:
线程池的直观起杀显示
主要围绕三个部分进行系统性质的阐述。
(1)排队子程序伪代码:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MainActivity extends AppCompatActivity {
private Button btn;
private Object lock=0;
private BlockingDeque<Thread> testQeque;
private boolean b=false;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=findViewById(R.id.btn)
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//testQeque=new LinkedBlockingDeque<>();
TestThread test=new TestThread();
for(int i=0;i<10;i++){
Thread t=new Thread(test);
t.start();
}
}
});
}
class TestThread implements Runnable{
DoThread doThread=new DoThread();
@Override
public synchronized void run() {
System.out.println("test "+Thread.currentThread().getName()+"开始执行");
new Thread(doThread).start();
b=false;
synchronized (lock){
while (b==false){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("test "+Thread.currentThread().getName()+"执行完毕");
}
}
class DoThread implements Runnable{
@Override
public void run() {
synchronized (lock){
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b=true;
lock.notify();
}
//System.out.println("do "+Thread.currentThread().getName()+"执行完毕");
}
}
}
(2)调度分配子程序伪代码:
每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程名、优先数、到达时间、需要运行时间、已用CPU时间、进程状态等等。进程的优先数、需要的运行时间及到达时间可以事先人为地指定(也可以由随机数产生)。进程的运行时间以时间片为单位进行计算。每个进程的状态可以是就绪 W(Wait)、运行R(Run)、或完成F(Finish)三种状态之一。就绪进程获得 CPU后都只能运行一个时间片。用已占用CPU时间加1来表示。
如果运行一个时间片后,进程的已占用 CPU时间已达到所需要的运行时间,则撤消该进程,如果运行一个时间片后进程的已占用CPU时间还未达所需要的运行时间,也就是进程还需要继续运行,此时应将进程的优先数减1(即降低一级),然后把它插入就绪队列等待CPU。每进行一次调度程序都打印一次运行进程、就绪队列、以及各个进程的 PCB,以便进行检查。 重复以上过程,直到所要进程都完成为止。
该工程主要有三个实现类:
Process(进程类),主要用来实例化各个不同的进程;
ProcessBlock(进程控制块类),用来为每个进程分配PCB,该类实例化后为进程类的类成员变量。
ProcessControl(进程控制类),为主类,用来调度进程。
在进程调度中,声明了三个队列,分别为待插入进程队列(按到达时间从小到大排序),就绪队列(按优先级从大到小排序,按照到达时间先后进行排序),完成队列。都是ArrayList<Process>类型变量。
调度算法描述:
1、程序开始时随机为初始化5个进程(程序太多不容易观察运行结果)。
2、声明时间变量t,while循环下调度程序一直运行,每运行一次,t++。
3、然后循环判断待插入队列队首进程是否到达,若到达,则将该进程插入到就绪队列中,并从待插入队列删除该进程;若没有到达,则从该循环中跳出。
4、然后从就绪队列中取出队首进程并分配时间片。当该进程时间片用完后,判断该进程是否已经完成,若完成,则将该进程插入到完成队列;若没有完成,则将该进程的优先级减一并重新插入到就绪队列中。
5、一直重复该循环,一直到待插入队列和就绪队列都为空为止。
所使用的数据结构(简化,仅成员变量,具体请见源码):
public class ProcessBlock {
private String name; //进程名
private int priority; //优先数 1-10
private int arriveTime; //到达时间 1-50
private int needTime; //需要时间 1-20
private int waitTime; //等待时间 1-20
private int alreadyUseTime; //已占用CPU时间
private char status; //进程状态 W, R, F
}
public class Process {
private ProcessBlock pcb;
}
public class ProcessControl {
private final static int uTime = 1; // 时间片
private static int time = 0; // 模拟时间
ArrayList<Process> processA = new ArrayList();// 待插入的进程
ArrayList<Process> processB = new ArrayList();// 就绪队列
ArrayList<Process> processFinish = new ArrayList();// 完成队列
}
采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法(将用户作业和就绪进程按提交顺序或变为就绪状态的先后排成队列,并按照先来先服务的方式进行调度处理)对就绪队列中的进程进行进程调度。
(3)上下文切换子程序伪代码
多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快. 然而实际情况是, 多线程之间需要竞争IO设备, 或者竞争锁资源,导致往往执行速度还不如单个线程. 在这里有一个经常提及的概念就是:上下文切换(Context Switch)。
多任务系统往往需要同时执行多道作业.作业数往往大于机器的CPU数, 然而一颗CPU同时只能执行一项任务, 如何让用户感觉这些任务正在同时进行呢? 操作系统的设计者巧妙地利用了时间片轮转的方式, CPU给每个任务都服务一定的时间, 然后把当前任务的状态保存下来, 在加载下一任务的状态后, 继续服务下一任务. 任务的状态保存及再加载, 这段过程就叫做上下文切换. 时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能, 但同时也带来了保存现场和加载现场的直接消耗。
(Note. 更精确地说, 上下文切换会带来直接和间接两种因素影响程序性能的消耗. 直接消耗包括: CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉; 间接消耗指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小).
(4)线程池的直观起杀显示伪代码
(1)
private Callable<String> callable;
private FutureTask<String> task;
private Thread thread;
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_start:
if (task == null || thread == null || task.isDone()) {
startThread();
showStatus();
}
break;
case R.id.tv_cancel:
if (task != null) {
task.cancel(cbCancel.isChecked());
showStatus();
}
break;
}
}
private void startThread() {
callable = new Callable<String>() {
int num = 0;
@Override
public String call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, TYPE_MSG_RUN);
Thread.sleep(100);
}
sendMsg(num, TYPE_MSG_DONE);
return "Done!";
}
};
task = new FutureTask<>(callable);
thread = new Thread(task);
thread.start();
}
(2)
private ExecutorService pool = Executors.newCachedThreadPool();
private Callable<Integer> callable1;
private Future<Integer> task1;
private Callable<Integer> callable2;
private Future<Integer> task2;
private Callable<Integer> callable3;
private Future<Integer> task3;
@Override
public void onClick(View v) {
super.onClick(v);
switch (v.getId()) {
case R.id.tv_start:
if (checkBox1.isChecked()) {
startThread1();
}
if (checkBox2.isChecked()) {
startThread2();
}
if (checkBox3.isChecked()) {
startThread3();
}
break;
case R.id.tv_start1:
startThread1();
break;
case R.id.tv_start2:
startThread2();
break;
case R.id.tv_start3:
startThread3();
break;
case R.id.tv_cancel:
if (checkBox1.isChecked()) {
cancelThread(task1);
}
if (checkBox2.isChecked()) {
cancelThread(task2);
}
if (checkBox3.isChecked()) {
cancelThread(task3);
}
break;
case R.id.tv_cancel1:
cancelThread(task1);
break;
case R.id.tv_cancel2:
cancelThread(task2);
break;
case R.id.tv_cancel3:
cancelThread(task3);
break;
case R.id.tv_shut_down:
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
}
break;
case R.id.tv_shut_down_now:
if (pool != null && !pool.isShutdown()) {
pool.shutdownNow();
}
break;
}
}
private void startThread1() {
if (task1 != null && !task1.isDone() || pool.isShutdown()) return;
callable1 = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, THREAD1_START);
Thread.sleep(100);
}
return 100;
}
};
task1 = pool.submit(callable1);
}
private void startThread2() {
if (task2 != null && !task2.isDone() || pool.isShutdown()) return;
callable2 = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, THREAD2_START);
Thread.sleep(100);
}
return 100;
}
};
task2 = pool.submit(callable2);
}
private void startThread3() {
if (task3 != null && !task3.isDone() || pool.isShutdown()) return;
callable3 = new Callable<Integer>() {
int num = 0;
@Override
public Integer call() throws Exception {
while (num < 100) {
num++;
sendMsg(num, THREAD3_START);
Thread.sleep(100);
}
return 100;
}
};
task3 = pool.submit(callable3);
}
private void cancelThread(Future<Integer> task) {
if (task != null && !pool.isShutdown()) {
task.cancel(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.item_newCachedThreadPool:
pool = Executors.newCachedThreadPool();
tvInfo.setText("当前线程池类型: newCachedThreadPool()");
return true;
case R.id.item_newSingleThreadExecutor:
pool = Executors.newSingleThreadExecutor();
tvInfo.setText("当前线程池类型: newSingleThreadExecutor()");
return true;
case R.id.item_newFixedThreadPool:
pool = Executors.newFixedThreadPool(2);
tvInfo.setText("当前线程池类型: newFixedThreadPool(2)");
return true;
case R.id.item_newScheduledThreadPool:
pool = Executors.newScheduledThreadPool(2);
tvInfo.setText("当前线程池类型: newScheduledThreadPool(2)");
return true;
default:
return super.onOptionsItemSelected(item);
}
}