进程与线程

进程和线程 #

进程 #

No.1、线程和进程的区别 #

可以从1、概念,2、所拥有的资源,3、开销 三个方面考虑。

  1. 从概念上来说,进程是资源分配的基本单位,线程是 CPU 调度的基本单位;
  2. 从资源的角度来讲,
    1. 一个进程可以有多个线程,线程间可以共享该进程的资源。比如说,进程有自己的独立地址空间,而这一个进程内的线程是共享这一份相同的地址空间的。这就意味着线程切换快,因为不需要切换页表。但同时,要是有一个线程挂掉了,整个进程也会挂掉。相比较之下,进程之间就不会有这种影响,也就是会比较健壮。
    2. 当然,除了共享的资源以外,线程也会有私有的一些资源,比如寄存器和栈。
  3. 从开销的角度来讲:
    1. 我们使用线程的时候,一方面是想要提高它的并发性,可以用多个线程去执行一个进程的不同部分,这样能充分发挥多CPU的功能。
    2. 另一方面是可以减少开销,比如
      1. 线程的创建时间比进程快,因为进程在创建的过程中,还需要分配内存、文件管理信息,而线程直接共享就可以了;
      2. 线程的终止会比进程快,同样的道理,它需要释放的资源会比较少;
      3. 线程切换快,因为同一个进程里面的线程都具有同一个页表,在切换的时候就不需要切换页表。
      4. 通信问题。线程因为会共享一些全局变量、静态变量等数据,因此线程间通信会方便一些,但也会带来同步和互斥的问题。进程间要通信就需要借助IPC。
    3. 共享的有:堆、全局变量、静态变量、指针,引用、文件
    4. 线程私有的有栈、寄存器等。

1. 概念:进程是操作系统资源分配的基本单位。 #

进程就是运行起来的可执行程序。我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行中的程序,就被称为「进程」。

2. 进程的状态:运行态、就绪态、阻塞态 #

  • 运行状态:该进程正在 CPU上跑;
  • 就绪状态:已经就绪,可运行,但还没有占有CPU,比如时间片用完了;
  • 阻塞状态:该进程暂停运行,等待某一事件发生(比如等待键盘输入),即使给它CPU控制权,它也无法运行;如果完成了,就变成就绪态。
  • 还有:创建状态、结束状态、挂起状态(表示进程没有占有内存空间,在硬盘上)

3. 进程的控制结构PCB #

是一个结构体,用来描述进程。也是进程存在的唯一标识。多个进程的PCCB通过链表的方式连接在一起。 包括:

  • 进程描述信息:进程标识符、用户标识符
  • 进程控制和管理信息:进程当前状态、进程优先级
  • CPU 相关信息:CPU 中各个寄存器的值
  • 资源:内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使用的 I/O 设备信息。

4. 进程的控制 #

01 创建进程 #

父进程创建子进程,子进程继承父进程所拥有的资源。

  1. 为子进程分配一个唯一的进程标识号,并申请一个空白的 PCB
  2. 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源;
  3. 初始化 PCB,等待被调度运行;

02 终止进程 #

正常结束、异常结束以及外界干预(信号 kill 掉)

终止进程的过程如下:

  • 查找 PCB;
  • 如果处于执行状态,则立即终止该进程的执行,然后将 CPU 资源分配给其他进程;
  • 如果还有子进程,终止所有子进程;
  • 将全部资源归还给父进程或操作系统;
  • 将其从 PCB 所在队列中删除;

03 阻塞进程 #

阻塞等待。

阻塞进程的过程如下:

  • 找到PCB;
  • 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行;
  • 将该 PCB 插入的阻塞队列中去;

04 唤醒进程 #

一旦被阻塞等待,它只能由另一个进程唤醒。 唤醒进程的过程如下:

  • 找到 PCB;
  • 将其从阻塞队列中移出,状态变为就绪状态;
  • 把该 PCB 插入到就绪队列中,等待调度程序调度;

5. 进程的上下文切换 #

CPU 寄存器和程序计数器(即将执行的下一条指令位置)。

什么时候会发生进程调度/上下文切换?

  • 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行;
  • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
  • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度;
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行;
  • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序

进程调度算法 #

  • 批处理系统(保证吞吐量和周转时间)
    • 先来先服务:利长不利短。
    • 短作业优先:长会饿死
    • 最短剩余时间优先:新作业加入时,比较
  • 交互式系统(快速响应)
    • 时间片轮转:排成队列,分配时间片,时钟中断
    • 优先级调度:为每个进程分配一个优先级
    • 多级反馈队列(结合):每个队列时间片大小都不同

进程同步 #

  • 临界区:对临界资源进行访问的那段代码
  • 互斥:多个进程同一时刻,只一个进程进入临界区
  • 信号量:PV操作,互斥量,生产者-消费者

进程间通信 #

  1. 无名管道:只能有亲缘,半双工

  2. 有名:没有亲缘关系的限制,半双工

  3. 信号:比如 ctrl+C,通知进程说某个事件已经发生

  4. 信号量:是一个计数器,用来控制多个进程对共享数据对象的访问

  5. 消息队列:消息的链表。

    1. 独立于发送进程和接收进程,即使进程终止,里面的内容不会删除
    2. 里面的消息可以用特定的格式和特定的优先级,所以可以支持随机读取,比如按消息类型读取,不用先进先出
    3. 克服了信号承载信息量少,管道只能无格式字节流和缓冲区大小受限的缺点
  6. 共享内存:最快的方式

  7. 套接字:适用不同机器

其他 #

3、一个进程可以创建多少线程,和什么有关? #

理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。如果要创建多于2048的话,必须修改编译器的设置。

因此,一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。如果需要创建超过2K以上的线程,减小你线程栈的大小就可以实现了,虽然在一般情况下,你不需要那么多的线程。过多的线程将会导致大量的时间浪费在线程切换上,给程序运行效率带来负面影响。