进程线程及其状态

Posted yanlei2018

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程线程及其状态相关的知识,希望对你有一定的参考价值。

进程线程及其状态

进程

进程的概念

  • 进程就是执行中的程序。

进程的状态

进程有五种状态,分别是:

  • 新建:进程正在被创建
  • 运行:进程正在被执行
  • 阻塞:进程等待某个事件(如I/O操作)
  • 就绪:进程等待分配处理器
  • 终止:进程完成执行

进程调度流程图

技术分享图片

线程

线程的概念

  • 线程是程序执行流的最小单元,线程早期也有轻量级进程之称。一个进程中可能包含多个线程。在系统内核层面,进程与线程并无本质的不同。进程与线程最大的不同点是资源分配。

线程与进程的比较

  • 线程与进程都可以实现多任务。
  • 线程是CPU调度的基本单元,进程是系统资源分配的基本单元。
  • windows下进程线程是泾渭分明,区别明显的。在Linux中它们有很多共同特性。
  • 在早期Linux的内核结构中:进程和线程的区别只是创建子进程和子线程时,是否设置为共享内存,二者在内核中的存储结构并无区别,系统调度的单位也是轻量级进程。2.6以后的Linux内核版本才将线程和进程完全独立开来。
  • 线程的状态改变只代表了CPU执行过程的改变,线程操作的资源仍然是进程的。除了 CPU外,计算机内的软硬件资源的分配都是以进程为单位的。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在,而线程只是进程的一部分,与进程内的其他线程一起共享分配给该进程的所有资源。

线程的状态

  • 同进程的实现原理类似,线程也可主要概括为五种状态(实际上Linux将线程状态细分为十几种):
    • 新建,由于不需要进行必要的内存复制等工作,新建线程要比新建进程更快。
    • 就绪
    • 运行
    • 阻塞
    • 死亡,线程死亡后,也需要回收处理。
  • 调度的过程参考进程。

线程的内核调度

  • 多线程编程具有响应度高、资源共享、经济和多处理器体系结构的利用四个优点。用户线程是映射到内核线程池进行CPU调度的,映射关系模型包含有:

    • (1)多对一
    • (2)一对一
    • (3)多对多

内核调度图

技术分享图片

  • 这里为什么没有一对多?因为线程是CPU资源调度的最小单位,即:单线程在一个时间点上只能利用到一个核心(进行一个原子操作),一个原子操作不能再分开由不同核心执行。而多核CPU在执行单线程任务时,可能会切换多个核心轮流来执行这个任务(每个原子操作的CPU核心可能并不相同),例如在执行循环时,这次循环和下次循环可能并不是同一个核心来执行的(这跟你的系统有关,但可以看到单线程最多只能占用到 (1/CPU核心数) 的CPU资源(超线程CPU占用1/(CPU线程数))。
  • 而资源上,多核CPU调用同一资源时,X86架构会使用总线锁,对该资源进行锁定,保证原子操作执行完整不被打断。当操作完成时,会解锁并通知其他线程,我操作完了,你们可以来操作了(实际上,此方法效率很低,仅作为最后一道保险)。
  • 因此确定一个操作是原子操作时,没有必要浪费外围昂贵的开销再来给他加锁,原子操作本身就是一道互斥锁。互斥锁的目的,也正是将一系列操作变为原子操作。

进程的诞生与消亡

  • 进程的诞生
    • (1)fork函数:子进程拷贝父进程的数据(具体实现是读时共享,写时复制)
    • (2)vfork函数:子进程与父进程共享数据
    • vfork是一个过时的函数,虽然与fork相比有那么一点性能优势,但其带来一连串的坑并不那么好填,不建议使用,除非你对性能追求到极致。
  • 进程的消亡
    • (1)正常结束和异常终止;
    • (2)linux系统设计时规定:每个进程结束时,系统会自动回收open但没有close的文件资源,malloc但没有free的资源等,但并不会回收进程本身占用的资源(即进程的尸体,主要包括进程本身的文件描述符对应的资源(task_struct结构体)和进程的栈空间),这需要由进程的父进程来完成回收(收尸)。
  • 僵尸进程
    • 在上述(2)中,如果父进程没有结束,而且也不回收已结束的子进程(收尸),已经结束的子进程,就变成了僵尸进程。
    • 父进程可以使用wait或waitpid,显式地回收子进程(剩余待回收)的内存资源并且获取子进程退出状态。
    • 父进程结束时也会自动回收僵尸进程,但应避免这种不严谨的方式。
  • 孤儿进程
    • 子进程还在执行,而父进程先结束了,子进程就成为了孤儿进程,托管到系统了。
    • 此时子进程的父进程变为了系统的init进程,init进程会在孤儿进程结束后自动回收孤儿进程的资源。

线程的诞生与消亡

  • 线程标识(线程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("我是子线程\\n");
    }
    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;
    }

以上是关于进程线程及其状态的主要内容,如果未能解决你的问题,请参考以下文章

线程理论及其运用

[Python3] 043 多线程 简介

进程和线程和协程之间的关系

线程学习知识点总结

线程的状态及其转换

线程及其创建的方式