美文网首页
进程与线程

进程与线程

作者: a4e794140953 | 来源:发表于2016-08-16 16:44 被阅读0次

    注意:

    • 多线程可以提高CPU利用率,不能提高内存利用率

    进程

    进程七状态转换图
    活动就绪------suspend------>静止就绪
    活动就绪<------active------静止就绪
    活动阻塞------suspend------>静止阻塞
    活动阻塞<------active------静止阻塞
    运行-->就绪 原因:时间片用完/被抢占(优先级等)
    进程挂起的原因有:
    1、终端用户的请求
    2、父进程的请求
    3、负荷调节的需要
    4、操作系统的需要
    进程出现故障将进入终止状态
    • wait()、notify()和notifyAll():Object类中的方法
      从这三个方法的文字描述可以知道以下几点信息:
      1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
      2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
      3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程
      4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程
      有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
    • Condition是个接口,基本的方法就是await()和signal()方法;(Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。)
      Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
      调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(); Condition中的signalAll()对应Object的notifyAll()
    • ThreadLocal类用于创建一个线程本地变量
      ThreadLocal存放的值是线程封闭,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递
      线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收
      在Thread类中有一个Map,用于存储每一个线程的变量的副本。
      对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式
    • sleep()
      sleep()是Thread类中的方法,而wait()则是Object类中的方法。sleep()方法导致了程序暂停,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。 wait()方法会导致线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
      注意是准备获取对象锁进入运行状态,而不是立即获得

    创建进程

    1. 申请空白PCB(进程控制块);
    2. 为新进程分派资源;
    3. 初始化PCB;
    4. 将新进程插入就绪队列;

    进程控制块PCB(Process Control Block)

    注意:

    Unix操作系统的进程控制块中常驻内存的是proc
    Linux创建进程的时候,同时创建进程控制块和进程对应的堆栈。该堆栈分为用户栈和系统栈,也就是核心栈。用户栈在用户态使用,用来保存局部变量,函数参数等,而系统栈在进程陷入内核态使用,里面保存的是用户栈的地址,以及在进行进程上下文切换时需要保存的参数,返回值等。因为每个进程都有可能发生系统调用陷入内核,从用户态到内核态发生一个中断,那么内核栈就会保存这个进程操作内核的调用信息,如果常驻内存的话就会被下个进程发生的系统调用的信息覆盖掉,所以内核栈是不会常驻内存的
    proc存放的是系统经常要查询和修改的信息,需要快速访问,因此常将其装入内存 。

    PCB一般包括

    1. 程序ID(PID、进程句柄):它是唯一的,一个进程都必须对应一个PID。PID一般是整型数字
    2. 特征信息:一般分系统进程、用户进程、或者内核进程等
    3. 进程状态:运行、就绪、阻塞,表示进程现的运行情况
    4. 优先级:表示获得CPU控制权的优先级大小
    5. 通信信息:进程之间的通信关系的反映,由于操作系统会提供通信信道
    6. 现场保护区:保护阻塞的进程用
    7. 资源需求、分配控制信息
    8. 进程实体信息,指明程序路径和名称,进程数据在物理内存还是在交换分区(分页)中
    9. 其他信息:工作单位,工作区,文件信息等

    Linux下多线程编程常用的pthread库提供的函数名和意义
    pthread_create 创建一个线程
    pthread_join用来等待一个线程的结束
    pthread_mutex_init 初始化一个线程互斥锁
    pthread_exit结束一个线程

    进程间通信(IPC,InterProcess Communication)

    通信方法:

    1. 高级通信: 文件记录及锁定,管道、信号、共享内存、Socket通信、远程过程调用(RPC),消息传递(直接:消息缓冲区;间接:信箱)等。
    2. 低级通信:管程,信号量
    • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
    • 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    • 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    • 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    • 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    • 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
    • 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

    fork

    fork()
    使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
    子进程与父进程的区别在于:
    1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)
    2、各自的进程ID和父进程ID不同
    3、子进程的未决告警被清除;
    4、子进程的未决信号集设置为空集。 -mickole博客

    • sleep()方法强制使当前线程休眠,释放CPU资源,以便使得其他所有线程有机会运行。
    • yield()方法使得当前的线程让出CPU的使用权,以使得比该线程优先级相同更高的线程有机会运行。该线程在让出CPU使用权之后可能再次被选中,因此yield()方法可能会不起作用(这也说明了yield()方法不会使得比当前线程优先级低的线程运行)。
    • java虚拟机中如果多个线程优先级相同,则会随机选择一个线程占用CPU,处于运行状态的线程会一直运行,直至它不得不放弃CPU为止,因此不一定是分时调度。
    • 分时调度模型是让所有线程轮流获得CPU使用权。

    进程互斥实现机制

    信号量
    对于记录型信号量,当 s<0 的时候,请求进程会阻塞
    对于整型信号量,当s<=0的时候,请求进程不会阻塞,而是进入盲等状态

    • -和+,DOWN()和UP(),SLEEP()和WAKEUP(),P()和V(),Wait() 和Signal()。虽然它们名字都不一样,可意思都是相同的。前者是申请资源,后者是释放资源。
      生产者与消费者
      生产者与消费者
      举例:
      单生产者单消费者:
    #define N 100
    typedef int semaphore;
    semaphore empty = N;
    semaphore full = 0;
    void producer(void)
    {
        int item;
        while(TRUE){
            item = produce_item();
            down(&empty);
            insert_item(item);
            up(&full);
        }
    }
    void consumer(void)
    {
        int item;
        while(TRUE){
            down(&full);
            item = remove_item();
            up(&empty);
            consume_item(item);
        }
    }
    

    多生产者多消费者:

    #define N 100
    typedef int semaphore;
    semaphore mutex = 1;
    semaphore empty = N;
    semaphore full = 0;
    void producer(void)
    {
        int item;
        while(TRUE){
            item = produce_item();
            down(&empty);
            down(&mutex);
            insert_item(item);
            up(&full);
            up(&mutex);
        }
    }
    void consumer(void)
    {
        int item;
        while(TRUE){
            down(&full);
            down(&mutex);
            item = remove_item();
            up(&empty);
            up(&mutex);
            consume_item(item);
        }
    }
    

    低级调度

    • 如果系统只有用户态线程,则线程对操作系统是不可见的,操作系统只能调度进程;
    • 如果系统中有内核态线程,则操作系统可以按线程进行调度;
    • 系统态,即内核态,三种情况下会出现由用户态转为内核态,中断,异常,系统调用

    相关文章

      网友评论

          本文标题:进程与线程

          本文链接:https://www.haomeiwen.com/subject/vxvpsttx.html