操作系统调度线程切换开销分析初探

       面向多片多核体系结构的操作系统线程调度已成为现代操作系统的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);
        }
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容