进程线程及其状态
进程
进程的概念
- 进程就是执行中的程序。
进程的状态
进程有五种状态,分别是:
- 新建:进程正在被创建
- 运行:进程正在被执行
- 阻塞:进程等待某个事件(如I/O操作)
- 就绪:进程等待分配处理器
- 终止:进程完成执行
进程调度流程图
线程
线程的概念
- 线程是程序执行流的最小单元,线程早期也有轻量级进程之称。一个进程中可能包含多个线程。一个进程至少包含一个线程,否则没有存在的意义。多线程里不宜再创建子进程。在系统内核层面,进程与线程并无本质的不同。进程与线程最大的不同点是资源分配。
线程与进程的比较
- 线程与进程都可以实现多任务。
- 线程是CPU调度的基本单元,进程是系统资源分配的基本单元。
- windows下进程线程是泾渭分明,区别明显的。在Linux中它们有很多共同特性。
- 在早期Linux的内核结构中:进程和线程的区别只是创建子进程和子线程时,是否设置为共享内存,二者在内核中的存储结构并无区别,系统调度的单位也是轻量级进程。2.6以后的Linux内核版本才将线程和进程完全独立开来。
- 线程的状态改变只代表了CPU执行过程的改变,线程操作的资源仍然是进程的。除了 CPU外,计算机内的软硬件资源的分配都是以进程为单位的。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在,而线程只是进程的一部分,与进程内的其他线程一起共享分配给该进程的所有资源。
线程的状态
- 同进程的实现原理类似,线程也可主要概括为五种状态(实际上Linux将线程状态细分为十几种):
- 新建,由于不需要进行必要的内存复制等工作,新建线程要比新建进程更快。
- 就绪
- 运行
- 阻塞
- 死亡,线程死亡后,也需要回收处理。
- 调度的过程参考进程。
线程的内核调度
-
多线程编程具有响应度高、资源共享、经济和多处理器体系结构的利用四个优点。用户线程是映射到内核线程池进行CPU调度的,映射关系模型包含有:
- 多对一
- 一对一
- 多对多
内核调度图
- 这里为什么没有一对多?因为线程是CPU资源调度的最小单位,即:单线程在一个时间点上只能利用到一个核心(进行一个原子操作),一个原子操作不能再分开由不同核心执行。而多核CPU在执行单线程任务时,可能会切换多个核心轮流来执行这个任务(每个原子操作的CPU核心可能并不相同),例如在执行循环时,这次循环和下次循环可能并不是同一个核心来执行的(这跟你的系统有关,但可以看到单线程最多只能占用到 (1/CPU核心数) 的CPU资源(超线程CPU占用1/(CPU线程数))。
- 而资源上,多线程调用同一资源时,X86架构可能会使用总线锁,对该资源进行锁定,保证原子操作执行完整不被打断。当操作完成时,会解锁并通知其他线程,我操作完了,你们可以来操作了(实际上,此方法效率很低,仅作为最后一道保险)。
- 因此确定一个操作是原子操作时,没有必要浪费外围昂贵的开销再来给他加锁,原子操作本身就是一道互斥锁。互斥锁的目的,也正是将一系列操作变为原子操作。
进程的诞生与消亡
- 进程的诞生
- (1)fork函数:子进程拷贝父进程的数据(具体实现是读时共享,写时复制)
- (2)vfork函数:子进程与父进程共享数据
- vfork是一个过时的函数,虽然与fork相比有那么一点性能优势,但其带来一连串的坑并不那么好填,不建议使用,除非你对性能追求到极致。
- 进程的消亡
- 正常结束和异常终止;
- 进程结束时的资源问题回收:linux系统设计时规定:每个进程结束时,系统会自动回收open但没有close的文件资源,malloc但没有free的资源等,但并不会回收进程本身占用的资源(即进程的尸体,主要包括进程本身的文件描述符对应的资源(task_struct结构体)和进程的栈空间),这需要由进程的父进程来完成回收(收尸)。
- 僵尸进程
- 在子进程消亡后,如果父进程没有结束,而且也不回收已结束的子进程(收尸),已经结束的子进程,就变成了僵尸进程。
- 父进程可以使用wait或waitpid,显式地回收子进程(剩余待回收)的内存资源并且获取子进程退出状态。
- 父进程结束时也会自动回收僵尸进程,但应避免这种不严谨的方式。
- 孤儿进程
- 子进程还在执行,而父进程先结束了,子进程就成为了孤儿进程,托管到系统了。
- 此时子进程的父进程变为了系统的init进程(该进程PID为1),init进程会在孤儿进程结束后自动回收孤儿进程的资源。
- 进程回收
- 可以使用wait或waitpid,阻塞回收进程资源,阻塞回收具有明显的劣势,会导致阻塞的父进程不能干别的事情了。
- Linux生产中更多的是采用信号机制,父进程注册信号,收到SIGCHLD信号才调用回收函数回收子进程资源,这样就不会导致父进程阻塞了。
线程的诞生与消亡
-
线程标识(线程ID)
- 进程ID在整个系统中是唯一的。
- 线程ID(pthread_t类型)只在它所属的进程中有效。
- pthread_t(Linux中为unsigned int类型,OS中为结构体指针,Windows中为handle句柄)用于声明线程ID。
- 函数:pthread_self取得自身线程ID。
-
创建线程
- 使用函数pthread_create,线程创建后,就开始运行相关的线程函数。
-
退出线程。
- 线程执行完毕。可以return,不能exit(exit是退出进程)。
- 使用函数pthread_exit,主动退出线程。主线程使用该函数时,进程并不会结束,而是等待其他线程结束。
- 进程结束时,线程也结束(线程依赖于其所在的进程)。
-
线程回收
- 由于线程使用的资源是属于进程的,退出线程而进程仍然运行时资源并未完全释放,形成僵尸线程。
- pthread_join(tid)函数类似wait/waitpid函数,用于阻塞等待线程tid结束,调用它的线程一直等待到tid线程结束时,tid线程资源就被回收。
- pthread_detach(tid)函数线程分离,让系统自动回收tid线程。
- 设置线程属性进行回收。可按以下步骤回收:
- pthread_attr_t attr;//线程创建前,定义线程属性
- pthread_attr_init(&attr);//进行初始化线程属性
- pthread_attr_getdetachsate(&attr,&status);//获取分离状态
- pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程分离状态.
- pthread_create(&tid, &attr,func,NULL);//创建线程
- pthread_attr_destroy(&attr);//线程结束时,调用回收函数
- 线程回收代码示例:
void * func(void *p) { printf("我是子线程 "); } int main(int argc, char *argv[]) { pthread_attr_t attr; //定义一个变量 pthread_t tid; pthread_attr_init(&attr);//初始化 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置分离 pthread_create(&tid, &attr, func, NULL);//创建线程 sleep(1);//等1秒让子线程执行完 pthread_attr_destroy(&attr);//释放 return 0; }