并发:
在操作系统中,某一个时间段中,几个程序在同一个CPU上运行,但是在任意一个时间点上,只有一个程序在CPU上运行。
当有多个线程时,如果系统只有一个CPU,那么CPU不可能真正同时运行多个线程,CPU的运行时间会被划分成若干个时间段,每个时间段分配给各个线程去执行,一个时间段内某个线程运行时,其他线程处于挂起状态,这就是并发。
并行:
当操作系统有多个CPU时,一个CPU处理A线程,另一个CPU处理B线程,两个线程互相不抢占CPU资源,可以同时进行,这种方式称为并行。
区别
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。从技术的角度上来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,再切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则没有内核切换的开销。
孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。
在类UNIX操作系统中,为避免孤儿进程退出时无法释放所占用的资源而僵死,任何孤儿进程产生时都会立即为系统进程init
自动接受为子进程,这一过程也称为”收养“。在此需注意,虽然事实上该进程已有init
作为其父进程,但由于创建该进程的进程已不存在,所以仍称为孤儿进程。
在类UNIX系统中,僵尸进程是指完成执行(通过exit
系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统的进程表中仍然存在其进程控制块,处于”终止状态“的进程。这发生于子进程需要保留表项以允许其父进程读取子进程的退出状态:一旦退出态通过wait
系统调用读取,僵尸进程条目就从进程表中删除,称之为”回收“。正常情况下,进程直接被其父进程wait
并由系统回收。进程长时间保持僵尸状态一般是错误的并导致资源泄露。
与正常进程不同,kill
命令对僵尸进程无效。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,但并不会变为僵尸进程,因为被init
收养并wait
其退出。
子进程死后,系统会发送SIGCHLD信号给父进程,父进程对其默认处理是忽略。如果想响应这个消息,父进程通常在信号事件处理程序中,使用wait
系统调用来响应子进程的终止。
僵尸进程被回收后,其进程号与在进程表中的表项都可以被系统重用。但如果父进程没有调用wait
,僵尸进程将保留进程表中的表项,导致了资源泄露。某些情况下这反倒是期望的:父进程创建了另外一个子进程,并希望具有不同的进程号。如果父进程通过设置事件处理函数为SIG_IGN
显式忽略SIGCHLD信号,而不是隐式默认忽略该信号,所有子进程的退出状态信息将被抛弃并且直接被系统回收。
收割僵尸进程的方法是通过kill
命令手工向其父进程发送SIGCHLD信号。如果其父进程仍然拒绝回收僵尸进程,则终止父进程,使得init
进程收养僵尸进程。init
进程周期执行wait
系统调用回收其收养的所有僵尸进程。
为避免产生僵尸进程,实际应用中一般采取的方式是:
fork
两次并杀死一级子进程,令二级子进程称为孤儿进程而被init
收养,清理。守护进程是生存长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。UNIX系统有很多守护进程,它们执行日常事务活动。
大多数守护进程都以超级用户root特权运行。所有的守护进程都没有控制终端,其终端名设置为问号。内核守护进程以无控制终端方式启动。用户层守护进程缺少控制终端可能是守护进程调用了setsid
的结构。大多数用户层守护进程都是进程组的组长进程以及会话的受进程,而且是这些进程组和会话的唯一进程。
应当引起注意的是用户层守护进程的父进程是init
进程。
每个进程除了有一个进程ID之外,还属于一个进程组。
进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。进程组ID类似于进程ID——它是一个正整数,并可存放在pid_t
数据类型中。
函数getpgrp
返回调用进程的进程组ID
#include <unistd.h> pid_t getpgrp(void); //返回值:调用进程的进程组ID
每个进程组有一个组长进程。组长进程的进程组ID等于其进程ID。
进程组组长可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长是否终止无关。从进程组创建开始到其中最后一个进程离开为止的事件区间称为进程组的生命期。某个进程组中的最后一个进程可以终止,也可以转移到另一个进程组。
一个进程只能为它自己或它的子进程设置进程组ID。在它的子进程调用exec
后,他就不能再更改子进程的进程组ID。
会话是一个或多个进程组的集合。
进程调用setsid
函数建立一个新会话。
#include <unistd.h> pid_t setsid(void); //返回值:若成功,返回进程组ID;若出错,返回-1
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。具体会发生以下3件事。
setsid
之前该进程有一个控制终端。如果在调用setsid
之前该进程有一个控制终端,那么这种联系被切断。如果该调用进程已经是一个进程组的组长,则此函数返回出错。为了保证不处于这种情况,通常先调用fork
,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组ID,而其进程ID则是新分配的,两者不可能相等,这就保证了子进程不是一个进程组的组长。