Java____进程、线程学习、处理器调度(转)

来源:互联网 发布:相思相见知何日 编辑:程序博客网 时间:2024/04/30 23:38

————————————线程、进程的状态、方法及同步——————————


#用于实现的主程序:

//一个线程只能start一次!//线程常见状态://创建状态:准备好了一个多线程对象//就绪状态:调用了start()方法 等待CPU进行调度//运行状态:执行了run()方法//阻塞状态:暂时停止执行 可能将资源交给其他线程使用//终止状态(死亡状态):线程销毁//线程常用方法://取得当前线程对象:currentThread()//判断线程是否启动:isAlive()//线程的强行运行:join()//线程的休眠:sleep()//线程的礼让:yield() Thread.yield() //取得当前线程对象: currentThread()//判断线程是否启动: isAlive()//线程的强行运行: join()//实例线程方法(多用于指定线程)://start:启动线程//setPriority:设置线程优先级//getPriority:获得线程优先级//取得线程名称:getName()//设置线程名称:setName()//设置线程幽灵状态: setDaemon()//取得线程幽灵状态:isDaemon()//线程优先级 有可能影响顺序 不是肯定影响! 某人是5  最小是1 最大是10public class ThreadStu0824 {/** * @param args */public static void main(String[] args) {//两种实现线程的方式://第一种方式:通过继承Thread类 调用写好的类 调用start才是并行执行 调用run还是顺序执行MyThread0824 threadA=new MyThread0824("A");MyThread0824 threadB=new MyThread0824("B");threadA.start();threadB.start();for(int i=0;i<100;i++){if(i>30){try {threadA.join();////A会完全执行完 而A线程结束后才启动主线程 所以主线程启动前的时间里其他线程会和A抢CPU //然后等A执行完后 主线程启动 除了A以外的线程再一起抢CPU//} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("主线程"+i);}//第二种方式:通过Runable实现多线程 编写好自己的Runnable后 要实现其抽象方法run //并且还是要通过线程来实现run 将自己写好的Run传入线程中MyRunable0824 r1 = new MyRunable0824("A");MyRunable0824 r2 = new MyRunable0824("B");Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start();//线程优先级//同步与死锁//1、同步代码块://在代码块上加上'synchronized'关键字 则此代码块就称为同步代码块//2、同步代码块格式://synchronized(同步对象){//需要同步的代码块;//}//3、同步方法://除了代码块可以同步  方法也可以同步//4、方法同步格式://synchronized void 方法名称(){//}//5、死锁://同步就是线程获得对象的锁,其他线程进入阻塞状态,就是一个池中等待。死锁就是AB线程分别获得CD对象的锁但A此时要调用D对象//可是B已经获得D对象的锁,导致A进入阻塞状态 又无法返回C的锁(解锁时可通过检测 若死锁就返回锁),导致B也无法退出返回锁D,进而AB死锁MyThreadDemo r = new MyThreadDemo();Thread t1 = new Thread(r);Thread t2 = new Thread(r);Thread t3 = new Thread(r);t1.start();t2.start();t3.start();}}class MyThreadDemo implements Runnable{private int ticket=8;public void run() {for(int i = 0;i<15;i++){//synchronized(this){//通过注释synchronized看区别 很有意思!B E不同步非常明显//if(ticket>0){//--ticket;//int now=ticket;//try {//System.out.println("B剩余车票:"+now);//Thread.sleep(2000);//} catch (InterruptedException e) {//// TODO Auto-generated catch block//e.printStackTrace();//}//System.out.println("E剩余车票:"+now);//}//if(ticket<=0){//System.out.println("余票不足!");//}//}//调用同步方法tell();}}//通过同步方法共享资源!public synchronized void tell(){if(ticket>0){--ticket;int now=ticket;try {System.out.println("B剩余车票:"+now);Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("E剩余车票:"+now);}if(ticket<=0){System.out.println("余票不足!");}}}


</pre><pre>
#主程序调用的线程类:

package com.Thread;public class MyThread0824 extends Thread{private String name;public MyThread0824(String name){this.name=name;}public void run(){for(int i=0;i<500;i++){System.out.println(name+":"+i);//if(i==100){//System.out.println("礼让!");//Thread.yield();//}}super.run();}}


#主程序调用的Runnable类:

package com.Thread;public class MyRunable0824 implements Runnable{private String name;public MyRunable0824(String name){this.name = name;}public void run(){for(int i=0;i<1000;i++){System.out.println(name+":"+i);}//这里不用super.run()是因为如果你继承自Thread类,你重写了其中的run方法可以super.run()将其中的方法内容包括进来//Thread中的run就有一个判断 if target is null}}


#生命周期:


stop()停止 suspend()挂起 resume()返回挂起 方法的使用过程中需要注意!容易引起死锁。

#线程间通信 wait notify()随机唤醒一个 从上次wait的位置开始执行 notifyAll()全部唤醒 然后一起争cpu

package com.ThreadComunication;import java.lang.Thread;import java.lang.Runnable;public class WNDemo {/** * @param args */public static void main(String[] args) {data d= new data();new Producer(d).start();new Consumer(d).start();}}class data {int i;public void add(){synchronized(this){i++;if(i%5==0){notifyAll();}}}public void sub(){synchronized(this){try {this.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("before:"+i);i-=5;System.out.println("after:"+i);}}}class Consumer extends Thread{data data;public Consumer(data data){this.data = data;}public synchronized void run(){while(true) data.sub();}}class Producer extends Thread{data data;public Producer(data data){this.data = data;}public synchronized void run(){while(true) data.add();}}



————————————同步--》锁的概念--》死锁产生——————————

一、同步问题提出

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。

复制代码
package cn.thread;public class Foo {    private int x = 100;    public int getX() {        return x;    }    public int fix(int y) {        x = x - y;        return x;    }}
复制代码
复制代码
package cn.thread;public class MyRunnable implements Runnable {    private Foo foo = new Foo();    public static void main(String[] args) {        MyRunnable run = new MyRunnable();        Thread ta = new Thread(run, "Thread-A");        Thread tb = new Thread(run, "Thread-B");        ta.start();        tb.start();    }    public void run() {        for (int i = 0; i < 3; i++) {            this.fix(30);            try {                Thread.sleep(1);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX());        }    }    public int fix(int y) {        return foo.fix(y);    }}
复制代码

运行结果:

Thread-B : 当前foo对象的x值= 40Thread-A : 当前foo对象的x值= 40Thread-B : 当前foo对象的x值= -20Thread-A : 当前foo对象的x值= -20Thread-B : 当前foo对象的x值= -80Thread-A : 当前foo对象的x值= -80

从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问Foo对象并修改其数据所致。

如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。

在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源类Foo变量x标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。

复制代码
package cn.thread;public class Foo2 {    private int x = 100;    public int getX() {        return x;    }    //同步方法    public synchronized int fix(int y) {        x = x - y;        System.out.println("线程"+Thread.currentThread().getName() + "运行结束,减少“" + y                + "”,当前值为:" + x);        return x;    }    //    //同步代码块//    public int fix(int y) {//        synchronized (this) {//            x = x - y;//            System.out.println("线程"+Thread.currentThread().getName() + "运行结束,减少“" + y//                    + "”,当前值为:" + x);//        }//        //        return x;//    }}
复制代码
复制代码
package cn.thread;public class MyRunnable2  {    public static void main(String[] args) {        MyRunnable2 run = new MyRunnable2();        Foo2 foo2=new Foo2();                MyThread t1 = run.new MyThread("线程A", foo2, 10);        MyThread t2 = run.new MyThread("线程B", foo2, -2);        MyThread t3 = run.new MyThread("线程C", foo2, -3);        MyThread t4 = run.new MyThread("线程D", foo2, 5);                t1.start();        t2.start();        t3.start();        t4.start();    }        class MyThread extends Thread {        private Foo2 foo2;        /**当前值*/        private int y = 0;                MyThread(String name, Foo2 foo2, int y) {            super(name);            this.foo2 = foo2;            this.y = y;        }        public void run() {            foo2.fix(y);        }    }}
复制代码
线程线程A运行结束,减少“10”,当前值为:90线程线程C运行结束,减少“-3”,当前值为:93线程线程B运行结束,减少“-2”,当前值为:95线程线程D运行结束,减少“5”,当前值为:90

二、同步和锁定

1、锁的原理

Java中每个对象都有一个内置锁。

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

当程序运行到synchronized同步方法或代码块时该对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
public int fix(int y) {
      synchronized (this) {
            x = x - y;
      }
      return x;
}

当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
public synchronized int getX() {
      return x++;
}

public int getX() {
      synchronized (this) {
            return x++;
      }
}
效果是完全一样的。

三、静态方法同步

要同步静态方法,需要一个用于整个类对象的锁,这个对象就是这个类(XXX.class)。
例如:
public static synchronized int setName(String name){
      Xxx.name = name;
}
等价于
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

四、如果线程不能获得锁会怎么样

如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。

当考虑阻塞时,一定要注意哪个对象正被用于锁定:
1、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上
3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。

五、何时需要同步

在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。

对于非静态字段中可更改的数据,通常使用非静态方法访问。
对于静态字段中可更改的数据,通常使用静态方法访问。

如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。已经超出SJCP考试范围了。

六、线程安全类

当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。

即使是线程安全类,也应该特别小心,因为操作的线程是间仍然不一定安全。

七、线程同步小结

1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。




————————————————线程、进程、处理器调度————————————————

(1)进程的概念(Dijkstra)

进程是可并发执行的程序在某个数据集合上的一次计算活动,也是操作系统进行资源分配和调度的基本单位。

(2)进程与程序的联系与区别

① 程序是指令的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。

② 程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。

注:程序可看作一个菜谱,而进程则是按照菜谱进行烹调的过程。

③ 进程和程序组成不同:进程是由程序、数据和进程控制块三部分组成的。

④ 进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

(3)进程的特征

动态性:进程是程序的执行,同时进程有生命周期。

并发性:多个进程可同存于内存中,能在一段时间内同时执行。

独立性:资源分配和调度的基本单位。

制约性:并发进程间存在制约关系,造成程序执行速度不可预测性,必须对进程的并发执行次序、相对执行速度加以协调。

结构特征:进程由程序块 、数据块、进程控制块三部分组成。

进程的三种基本状态:

(1)运行态(running)

当进程得到处理机,其执行程序正在处理机上运行时的状态称为运行状态。

在单CPU系统中,任何时刻最多只有一个进程处于运行状态。在多CPU系统中,处于运行状态的进程数最多为处理机的数目。

(2)就绪状态(ready)

当一个进程已经准备就绪,一旦得到CPU,就可立即运行,这时进程所处的状态称为就绪状态。系统中有一个就绪进程队列,处于就绪状态进程按某种调度策略存在于该队列中。

(3)等待态(阻塞态)(Wait / Blocked )

若一个进程正等待着某一事件发生(如等待输入输出操作的完成)而暂时停止执行的状态称为等待状态。      处于等待状态的进程不具备运行的条件,即使给它CPU,也无法执行。系统中有几个等待进程队列(按等待的事件组成相应的等待队列)。

进程、线程与处理器的调度

运行到等待:等待某事件的发生(如等待I/O完成)

等待到就绪:事件已经发生(如I/O完成)

运行到就绪:时间片到(例如,两节课时间到,下课)或出现更高优先级进程,当前进程被迫让出处理器。

就绪到运行:当处理机空闭时,由调度(分派)程序从就绪进程队列中选择一个进程占用CPU。

  上述三种状态是进程最基本的状态,在实际的操作系统实现中,进程远不止这三种状态。

进程、线程与处理器的调度
进程为什么要有“挂起”状态?

      由于系统不断创建进程,系统资源特别是主存已不能满足进程运行要求,此时必须将某些进程挂起(suspend),置于磁盘对换区,释放其所占资源,暂时不启用低级调度,起到平滑负载的目的。

进程、线程与处理器的调度
进程的描述和组成

进程内容及其状态集合称为进程映像。包括:

进程控制块:每个进程有一进程控制块,用来存储进程的标识信息、现场信息和控制信息。

程序块:

核心栈:每个进程捆绑一个核心栈,进程在核心态工作时,用来保存中断/异常现场等。

数据块:存放程序私有数据,用户栈也在数据块中开辟。

进程上下文

操作系统中把进程物理实体和支持进程运行的环境合称为进程上下文。

进程在其当前上下文中运行,当系统调度新进程占有处理器时,新老进程随之发生上下文切换。即保存老进程状态而装入被保护了的新进程的状态,以便新进程运行

进程上下文组成

用户级上下文:由正文(程序)、数据、共享存储区、用户栈组成,占用进程的虚地址空间。

存器上下文:由程序状态字寄存器、指令计数器、栈指针、控制寄存器、通用寄存器等组成。

系统级上下文:由进程控制块、主存管理信息(页表或段表)、核心栈等组成。

进程控制块(Process Control Block,PCB)

每个进程有且仅有一个进程控制块

PCB是操作系统用于记录和刻划进程状态及有关信息的数据结构,是操作系统掌握进程的唯一资料结构。

系统利用PCB来控制和管理进程,所以PCB是系统感知进程存在的唯一标志

进程与PCB是一一对应的,在创建进程时,建立PCB,并伴随进程运行的全过程,直到进程撤消而撤消。PCB就象我们的户口。

PCB的内容

①标识信息

进程标识ID:唯一,通常是一个整数

进程组标识ID

用户进程名

用户组名

②现场信息

寄存器内容(通用寄存器内容、控制寄存器内容、栈指针等)

③控制信息

进程调度信息:如进程状态、等待时间、等待原因、进程优先级、队列指针等

进程组成信息:如正文段指针、数据段指针、进程族系信息

进程间通信信息:如消息队列指针、所使用的信号量和锁

进程段、页表指针、进程映像在辅存地址

CPU的占用和使用信息:如时间片剩余量、已占用CPU时间、已执行时间总和、定时器信息、记账信息

进程特权信息:如主存访问权限、处理器特权

资源清单:所需全部资源、已分得资源

进程队列及其管理

处于同一状态的所有PCB组织在一起的数据结构称为进程队列。例如运行队列、就绪队列、等待队列。

同一状态进程的PCB既可按先来先到的原则排成队列;也可按优先数或其它原则排成队列。

通用队列组织方式:

    线性方式

    链接方式

    索引方式

(1)线性方式

OS根据进程的最大数目,静态分配主存中某块空间,所有进程的PCB都组织在一个线性表中。

优点:简单易行;

缺点:限定了系统中进程最大数,

      经常要扫描整个线性表,调度效率较低。

(2)链接方式

相同状态的进程PCB通过链接指针链接成一个队列。

不同状态的进程可排成不同的队列,如运行队列、就绪队列、等待队列。等待队列按等待原因不同可排成多个等待队列。

(3)索引方式

对具有相同状态的进程,分别设置各自的PCB索引表,如就绪索引表、等待索引表,记录PCB在PCB表中的地址.

进程切换

一个进程让出处理器,由另一个进程占用处理器的过程称为进程切换。

进程的切换使系统中的各进程均有机会占用CPU。

进程切换的步骤

保存被中断进程的处理器现场信息

修改被中断进程的进程控制块的有关信息,如进程状态等

把被中断进程的进程控制块加入有关队列

选择下一个占有处理器运行的进程

修改被选中进程的进程控制块的有关信息

根据被选中进程设置操作系统用到的地址转换和存储保护信息

根据被选中进程恢复处理器现场

进程的控制和管理

进程是有生命周期的:产生、运行、暂停、终止。进程生命周期的动态变化过程由进程管理程序来控制。

进程的控制和管理包括:

   进程创建

   进程撤消

   进程阻塞

   进程唤醒

   进程挂起

   进程激活

这些控制和管理功能由操作系统中的原语实现。

原语是在核心态执行、完成系统特定功能的不可分割的过程。

原语的特点是执行过程中不允许被中断,是一个不可分割的基本单位,原语的执行是顺序的而不可能是并发的。

1.进程创建

进程创建类似于人出生后要到派出所报户口。

进程创建过程:

(1)在进程列表中增加一项,从PCB池中申请一个空闲PCB,为新进程分配惟一的进程标识符;

(2)为新进程的进程映像分配地址空间。进程管理程序确定加载到进程地址空间中的程序;

(3)为新进程分配除主存空间外的其他各种所需资源;

(4)初始化PCB,如进程标识符、处理器初始状态、进程优先级等;

(5)把新进程状态置为就绪态,并移入就绪进程队列;

(6)通知操作系统的某些模块,如记账程序、性能监控程序。

2.进程撤销

进程完成其任务或出现严重错误后,操作系统调用进程撤消原语撤消进程。相当于一个人死亡后,家人要去派出所消户口。

进程撤销过程:

(1)根据撤销进程标识号,从相应队列中找到并移出它;

(2)将该进程拥有的资源归还给父进程或操作系统;

(3)若该进程拥有子进程,先撤销它的所有子进程,以防它们脱离控制;

(4)回收PCB,并归还到PCB池。

3.进程阻塞和唤醒

当一个处在运行状态的进程,因等待某个事件的发生(如等待打印机)而不能继续运行时,进程将调用阻塞原语来阻塞自己,进程的状态由运行态转换为等待态(阻塞态)。

当等待事件完成时,会产生一个中断,激活操作系统,在系统控制下将被阻塞的进程唤醒,这个进程将由阻塞状态转换成就绪状态。

进程、线程与处理器的调度 

进程阻塞步骤:

(1)停止进程执行,保存现场信息到PCB

(2)修改进程PCB有关内容,如进程状态由运行态改为等待态等,并把修改状态后的进程移入相应事件的等待队列中;

(3)转入进程调度程序去调度其他进程运行。

进程唤醒步骤:

(1)从相应的等待队列中移出进程;

(2)修改进程PCB的有关信息,如进程状态改为就绪态,并移入就绪队列;

(3)若被唤醒进程比当前运行进程优先级高,重新设置调度标志。

线程及其实现

一、引入多线程的动机

引入进程的目的是为了使多个程序并发执行,以改善资源使用率、提高系统效率。

再引入线程,则是为了减少程序并发执行时所付出的时空开销,使得并发粒度更细、并发性更好。

进程的两项功能

1. 进程是资源分配和保护基本单位。

2.进程同时又是一个可独立调度和分派的基本单位。  

    进程作为一个资源拥有者,在创建、撤消、切换中,系统必须为之付出较大时空开销。所以系统中进程的数量不宜过多,进程切换的频率不宜过高,但这也就限制了并发程度的进一步提高。

为解决此问题,人们想到将进程的上述两个功能分开,即对作为调度和分派的基本单位,不同时作为独立分配资源的单位;对拥有资源的单位,不对之进行频繁切换,线程因而产生。

多线程环境中进程的定义

进程是操作系统中除处理器外进行的资源分配和保护的基本单位,它有一个独立的虚拟地址空间,用来容纳进程映像(如与进程关联的程序与数据),并以进程为单位对各种资源实施保护,如受保护地访问处理器、文件、外部设备及其他进程(进程间通信)。

1.多线程环境中的线程概念

线程是操作系统进程中能够并发执行的实体,是处理器调度和分派的基本单位。

每个进程内可包含多个可并发执行的线程。

线程自己基本不拥有系统资源,只拥有少量必不可少的资源:程序计数器、一组寄存器、栈。

同属一个进程的线程共享进程所拥有的主存空间和资源。

2.引入线程的好处

创建一个新线程花费时间少(结束亦如此)

两个线程的切换花费时间少

因为同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核

3. 线程与进程的比较

线程具有进程的许多特征,故又称轻型进程,传统进程称重型进程。

在引入线程的OS中,每一进程都拥有多个线程,至少一个。

(1)调度

在传统OS中,拥有资源、独立调度和分派的基本单位都是进程,在引入线程的系统中,线程是调度和分派的基本单位,而进程是拥有资源的基本单位。

在同一个进程内线程切换不会产生进程切换,由一个进程内的线程切换到另一个进程内的线程时,将会引起进程切换。

(2)并发性

在引入线程的系统中,进程之间可并发,同一进程内的各线程之间也能并发执行。因而系统具有更好的并发性。

(3)拥有资源

无论是传统OS,还是引入线程的OS,进程都是拥有资源的独立单位,线程一般不拥有系统资源,但它可以访问隶属进程的资源。即一个进程的所有资源可供进程内的所有线程共享。

(4)系统开销

进程的创建和撤消的开销要远大于线程创建和撤消的开销,进程切换时,当前进程的CPU环境要保存,新进程的CPU环境要设置,线程切换时只须保存和设置少量寄存器,并不涉及存储管理方面的操作,可见,进程切换的开销远大于线程切换的开销。

同时,同一进程内的各线程由于它们拥有相同的地址空间,它们之间的同步和通信的实现也变得比较容易。

线程的实现

多线程的实现分为三类:

用户级线程(User Level Thread,ULT):对于这种线程的创建、撤消、和切换,由用户程序来实现,内核并不知道用户级线程的存在。

内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。

混合式线程:同时支持ULT和KLT两种线程。

1.用户级线程(ULT)

由应用程序完成所有线程的管理

通过用户空间中的线程库来完成

内核并不知道线程的存在。

线程库

提供线程运行管理系统:

创建、撤消线程

在线程之间传递消息和数据

调度线程执行

保护和恢复线程上下文

用户级线程的优点和缺点

优点:

线程切换不调用核心

调度是应用程序特定的:可以按需要选择好的算法

ULT可运行在任何操作系统上(只需要线程库),可以在一个不支持线程的OS上实现 

缺点:

由于大多数系统调用是阻塞的,因此一个用户级线程的阻塞会引起整个进程的阻塞。

核心只将处理器分配给进程,同一进程中的两个线程不能同时运行于两个处理器上

2.核心级线程(KLT)

所有线程管理由核心完成

没有线程库,但核心提供线程API来使用线程

核心维护进程和线程的上下文

线程之间的切换需要核心支持

以线程为基础进行调度

核心级线程的优点和缺点

优点:

对多处理器,核心可以同时调度同一进程的多个线程

阻塞是在线程一级完成

缺点:

在同一进程内的线程切换调用内核,系统开销较大

3.混合式线程

既支持用户级线程,又支持内核级线程。

例子:Solaris

处理器调度

处理机是计算机系统中的重要资源。

处理机调度算法对整个计算机系统的综合性能指标有重要影响。

可把处理机调度分成三个层次:

高级调度

中级调度

低级调度

1.高级调度

也称为作业调度或长程调度。

作业调度的主要功能是根据作业调度算法选择外存上处于后备队列中的某些作业调入内存,并为他们分配必要的资源、创建作业相应的进程,在作业完成后还要做结束阶段的善后工作。

2.低级调度

也称进程/线程调度、短程调度。

进程调度的主要功能是根据一定的调度算法从就绪队列中选中一个进程/内核级线程获得处理器,让它使用。

低级调度是操作系统最核心部分,执行十分频繁,其调度策略的好坏直接影响整个系统的性能。

低级调度的调度方式:

(1)非剥夺式(非抢先式)

调度程序一旦把cpu分配给某一进程/线程后,便让他一直运行下去,直到进程完成或发生某事件不能运行时,才将cpu分配给其他进程。

这种调度方式通常用于批处理系统中。     

优点:简单,系统开销小

缺点:难以满足紧急任务的要求,实时系统不宜采用

(2)剥夺式(抢先式)

当一个进程/线程正在处理器上执行时,调度程序可根据某种原则剥夺cpu分配给其他进程/线程。

这种调度方式通常用于分时系统和实时系统中。 

剥夺的原则:

优先权原则

短作业(进程)优先原则

时间片原则

3.中级调度

又称平衡调度,中程调度

涉及进程在内外存间的交换,当主存资源紧缺时将暂不运行的进程从内存调至外存,此时这个进程处于“挂起”状态;当进程又具备运行条件且主存资源有空闲时,再将进城从外存调至内存。

中级调度的主要目的是提高内存利用率和系统吞吐量。

低级调度是各类操作系统必备的,在纯粹的分时系统或实时系统中,通常不需高级调度。一般系统都有高级调度和低级调度;功能完善的系统引入了中级调度。

选择调度算法的原则

.资源利用率(特别是CPU利用率)

 CPU利用率=CPU有效工作时间/CPU总的运行时间,

 CPU总的运行时间=CPU有效工作时间+CPU空闲等待时间。 

2.吞吐率

  单位时间内处理的作业数。

  评价批处理系统性能的另一个重要指标。 

3.公平性

  确保每个用户每个进程获得合理的CPU份额或其他资源份额,不会出现饿死情况。

4.响应时间

交互式进程从提交一个请求(命令)到接收到响应之间的时间间隔称响应时间。

响应时间包括:请求传送到CPU时间、CPU处理请求的时间、响应回送到终端显示器的时间。

使交互式用户的响应时间尽可能短,或尽快处理实时任务,这是分时系统和实时系统衡量调度性能的一个重要指标。

5.周转时间

批处理用户从作业提交给系统开始,到作业完成为止的时间间隔称作业周转时间。

包括四部分时间:在外存后备队列上等待作业调度的时间,相应进程在就绪队列中等待进程调度的时间,进程在cpu上执行的时间、进程等待I/O操作完成的时间。

应使作业周转时间或平均作业周转时间尽可能短,这是批处理系统衡量调度性能的一个重要指标。

6.周转时间ti

    作业i提交给系统的时刻是ts,完成时刻是tf,该作业的周转时间ti为:ti = tf – ts。

周转时间=作业等待时间+作业运行时间。

 

为了提高系统的性能,要让若干个用户的平均作业周转时间和平均带权周转时间最小。

 

平均作业周转时间 T

       T = (Σti) / n

带权周转时间 wi

      如果作业i的周转时间为ti,所需运行时间为tk,则

          wi=ti /tk

 

平均作业带权周转时间 W

        W = (Σwi) / n

作业与进程的关系

1. 作业的基本概念

(1)作业(job)

    用户提交给操作系统计算的一个独立任务。

(2)作业步(job step)

    一个作业可划分成若干加工步骤,称为一个作业步。

    典型的作业控制过程:

  “编译”、“链接” “装入”、“运行”

(3)作业控制块( Job Control Block ,JCB)

为有效地管理作业,必须为进入系统的每个作业建立作业控制块。JCB是在批作业进入系统时,由Spooling系统建立的,它是作业存在于系统的标志,作业撤离时,JCB也被撤销。

JCB保存有系统对于作业进行管理所需要的全部信息。

批处理作业的组织和管理

一个作业从进入系统到运行结束经历四个不同的状态:

输入状态:

后备状态:

执行状态:

完成状态:

进程、线程与处理器的调度

 

作业是任务实体,进程是完成任务的执行实体;没有作业任务,进程无事可干,没有进程,作业任务没法完成。

作业概念更多地用在批处理操作系统,而进程则可以用在各种多道程序设计系统。

处理器调度算法

一、先来先服务(First Come First Served,FCFS )

 最简单的调度算法,即可用于作业调度,也可用于进程调度。

按作业(进程)来到的先后次序进行调度。

优点:易于实现

缺点:调度程序每次选择的作业是等待时间最久的,而不管作业的运行时间的长短,此算法效率低;

      有利于长作业,不利于短作业

二、最短作业(进程)优先算法
SJF:Shortest Job First
SPF:Shortest Process First

可用于作业调度和进程调度。

估计作业(进程)的CPU运行时间,选取估计时间最短的作业(进程)投入运行。

优点:

(1)易于实现。

(2)在一般情况下这种调度算法比先来先服务调度算法的调度性能比FCFS好。 

缺点:

(1)作业(进程)的执行时间是用户估计的,不一定准确,所以实现时不一定真正做到短作业优先调度。

(2)对长作业不利

   若系统不断接受新作业,就有可能使长作业长时间得不到调度。出现饥饿现象

(3)缺少剥夺机制,对分时、实时系统仍不理想

三、响应比最高者优先算法
(HRRF:Highest Response Ratio First)

FCFS与SJF是片面的调度算法。FCFS只考虑作业等候时间而忽视了作业的计算时问,SJF只考虑用户估计的作业计算时间而忽视了作业等待时间。

HRRF是介乎这两者之间的折衷算法,既考虑作业等待时间,又考虑作业的运行时间,既照顾短作业又不使长作业的等待时间过长,改进了调度性能。

响应比R = 作业周转时间 / 作业处理时间

        =(作业处理时间+作业等待时间)/ 作业处理时间

        = 1 +(作业等待时间 / 作业处理时间)

响应比最高者优先算法:每次调度时,计算所有作业的响应比,选择响应比最高的调度。 

短作业容易得到较高响应比,  

长作业等待时间足够长后,也将获得足够高的响应比,饥饿现象不会发生。

计算响应比导致一定的时间开销,此算法性能介于FCFS和SJF之间。

四、优先级调度算法

为作业或进程确定优先级,选择优先级最高的作业或进程调度。

1.两种方式

非剥夺式:某一进程被调度运行后,除非由于它自身的原因不能运行,否则一直运行下去。

剥夺式:当有比正在运行的进程优先级更高的进程就绪时,系统可强行剥夺正在运行进程的CPU,提供给具有更高优先级的进程使用。

 采用这种调度算法的关键是如何确定进程的优先级、一个进程的优先级确定之后是固定的,还是随着该进程运行的情况的变化而变化。

2.优先级类型

(1)静态优先级

    在进程创建时确定优先级,在进程运行时保持不变。

    确定优先级方法:

系统确定(内部优先级) :考虑进程运行时间、使用资源,进程类型。

用户确定(外部优先级) :考虑进程紧迫程度,计费与进程优先级有关。

(2)动态优先级

     在进程创建时创立一个优先级,系统在运行的过程中,根据系统的设计目标,不断地调整进程的优先级,这种方法的优点是能比较客观地反映进程的实际情况和保证达到系统设计目标。

     如等待时间长优先级可改变 。         

五、时间片轮转调度算法(Round Robin,RR)

分时系统中常用时间片轮转法。

把CPU划分成若干时间片,并且按顺序分配给就绪队列中的每一个进程,进程轮流占有CPU,当时间片用完时,即使进程未执行完毕,系统也剥夺该进程的CPU,将该进程排在就绪队列末尾,等候下一轮调度。

1.时间片大小的确定

时间片大小对系统性能有很大影响。时间片太小,会导致频繁切换,增大系统开销;时间片太大,轮转一次时间加长,进程在一个时间片内完成,时间片轮转算法退化为FCFS算法,无法满足交互式用户需求。

确定时间片长度要从进程数目、切换开销、系统效率和响应时间等多方面加以考虑。

2.RR改进

RR由于采用固定时间片和仅有一个就绪队列,所以服务质量不够理想,进一步改进沿两个方向:

(1)将固定时间片改为可变时间片

     引入可变时间片轮转调度算法

(2)将单就绪队列改为多就绪队列

     引入多级反馈队列调度算法

六、多级反馈队列调度算法

(MLFQ:Multi-level Feedback Queue)

设置多个就绪队列,并为各个队列赋予不同的优先级,第一个队列最高,第二个次之……;各个队列时间片大小也不同,在优先级越高的队列中,为每个进程分配的时间片越小。

处理器调度先从第一个就绪进程队列中选取进程,同一队列中的进程按FCFS算法进行排队。只有在未选到时,才从较低级的就绪进程队列中选取。

当新进程进入内存后,首先放在第一个队列末尾,到轮到该进程执行时,如在该时间片完成,便可撤离系统;若未完成,则将该进程转入第二个队列末尾…

该算法具有较好的性能,能满足各类应用的需要。

分时交互型短作业:通常在第一队列规定的时间片完成,可使终端型用户满意。

短批处理作业:通常在第一队列和第二队列各执行一个时间片就能完成,周转时间仍很短。

长批处理作业:它将一次在第1,2,3…n个队列中运行。

0 0