zoukankan      html  css  js  c++  java
  • Liunx系统下的进程与线程

    1.    进程、线程的概念

    a.    进程是操作系统进行资源分配的单位。

    b.    线程(Thread)是程序中独立的指令流,是CPU调度和分派的基本单位。

    c.     多进程是指同时运行多种程序。或者一个程序多个进程。

    d.    多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率;

    进程和线程的主要差别:进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率 要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程

    2.    进程、线程的区别?

    1).    相同点:

           无论是进程还是线程,对于程序员而言,都是用来实现多任务并发的技术手段。二者都可以独立调度,因此在多任务环境下,功能上并无差异。并且二者都具有各自的实体,是系统独立管理的对象个体。所以在系统层面,都可以通过技术手段实现二者的控制。而且二者所具有的状态都非常相似。而且,在多任务程序中,子进程(子线程)的调度一般与父进程(父线程)平等竞争。

    2).    实现方式的差异:

           进程是资源分配的基本单位,线程是调度的基本单位。进程的个体间是完全独立的,而线程间是彼此依存的。多进程环境中,任何一个进程的终止,不会影响到其他进程。而多线程环境中,父线程终止,全部子线程被迫终止(没有了资源;前提没有线程分离)。而任何一个子线程终止一般不会影响其他线程,除非子线程执行了exit()系统调用。任何一个子线程执行exit(),全部线程同时灭亡。

              i.        不存在只有线程而没有进程的程序。多线程程序中至少有一个主线程,而这个主线程其实就是有main函数的进程。它是整个程序的进程,所有线程都是它的子线程。我们通常把具有多线程的主进程称之为主线程。

              ii.        fork()是将父进程的全部资源复制给了子进程。而线程的clone只是复制了一小部分必要的资源。在调用clone时可以通过参数控制要复制的对象。可以说,fork实现的是clone的加强完整版。当然,后来操作系统还进一步优化fork实现——写时复制技术。在子进程需要复制资源(比如子进程执行写入动作更改父进程内存空间)时才复制,否则创建子进程时先不复制。

              iii.        vfork()这也是一个系统调用,用来创建一个新的进程。它创建的进程并不复制父进程的资源空间,而是共享,也就说实际上vfork实现的是一个接近线程的实体,只是以进程方式来管理它。并且,vfork()的子进程与父进程的运行时间是确定的:子进程“结束”后父进程才运行。请读者注意“结束”二字。并非子进程完成退出之意,而是子进程返回时。一般采用vfork()的子进程,都会紧接着执行execv启动一个全新的进程,该进程的进程空间与父进程完全独立不相干,所以不需要复制父进程资源空间。此时,execv返回时父进程就认为子进程“结束”了,自己开始运行。实际上子进程继续在一个完全独立的空间运行着。举个例子,比如在一个聊天程序中,弹出了一个视频播放器。你说视频播放器要继承你的聊天程序的进程空间的资源干嘛?莫非视频播放器想要窥探你的聊天隐私不成?懂了吧!

    3).     多任务程序设计模式的区别:

              i.        资源独立的优点:由于进程间是独立的,所以在设计多进程程序时,需要做到资源独立管理时就有了天然优势,而线程就显得麻烦多了。比如多任务的TCP程序的服务端,父进程执行accept()一个客户端连接请求之后会返回一个新建立的连接的描述符DES,此时如果fork()一个子进程,将DES带入到子进程空间去处理该连接的请求,父进程继续accept等待别的客户端连接请求,这样设计非常简练,而且父进程可以用同一变量(val)保存accept()的返回值,因为子进程会复制val到自己空间,父进程再覆盖此前的值不影响子进程工作。但是如果换成多线程,父线程就不能复用一个变量val多次执行accept()了。因为子线程没有复制val的存储空间,而是使用父线程的,如果子线程在读取val时父线程接受了另一个客户端请求覆盖了该值,则子线程无法继续处理上一次的连接任务了。改进的办法是子线程立马复制val的值在自己的栈区,但父线程必须保证子线程复制动作完成之后再执行新的accept()。但这执行起来并不简单,因为子线程与父线程的调度是独立的,父线程无法知道子线程何时复制完毕。这又得发生线程间通信,子线程复制完成后主动通知父线程。这样一来父线程的处理动作必然不能连贯,比起多进程环境,父线程显得效率有所下降。(可以在主线程直接分配较大的Val数组存放,保证子线程可以共享使用)。

             ii.        共享数据的优点:多进程环境间完全独立,要实现通信的话就得采用进程间的通信方式,它们通常都是耗时间的。而线程则不用任何手段数据就是共享的。当然多个子线程在同时执行写入操作时需要实现互斥,否则数据就写“脏”了。

    4).    实体间(进程间,线程间,进线程间)通信方式的不同

            i.        进程间的通信方式有这样几种:

          A.共享内存    B.消息队列    C.信号量      D.有名管道    E.无名管道    F.信号    G.文件      H.socket

            ii.        线程间的通信方式上述进程间的方式都可沿用(除去F.信号:基于进程为单位),且还有自己独特的几种:

          A.互斥量      B.条件变量     C.自旋锁      D.读写锁       E.线程信号     G.全局变量

           iii.        通讯速度:进程间采用的通信方式要么需要切换内核上下文,要么要与外设访问(有名管道,文件)。所以速度会比较慢。而线程采用自己特有的通信方式的话,基本都在自己的进程空间内完成,不存在切换,所以通信速度会较快。

           iv.        总结之,进程间的通信方式都是脱离于进程本身存在的,是全系统都可见的。这样一来,进程的单点故障并不会损毁数据,当然这不一定全是优点。比如,进程崩溃前对信号量加锁,崩溃后重启,然后再次进入运行状态,此时直接进行加锁,可能造成死锁,程序再也无法继续运转。再比如,共享内存是全系统可见的,如果你的进程资源被他人误读误写,后果肯定也是你不想要的。所以,各有利弊,关键在于程序设计时如何考量,技术上如何规避。

    5).    控制方式的异同

    进程与线程的身份标示ID管理方式不一样:

      i.        进程的ID为pid_t类型,实际为一个int型的变量(也就是说是有限的),在全系统中,进程ID是唯一标识,对于进程的管理都是通过PID来实现的。每创建一个进程,内核去中就会创建一个Task_Struct结构体来存储该进程的全部信息,每一个存储进程信息的节点也都保存着自己的PID。需要管理该进程时就通过这个ID来实现(比如发送信号)。当子进程结束要回收时(子进程调用exit()退出或代码执行完),需要通过wait()系统调用来进行,未回收的消亡进程会成为僵尸进程,其进程实体已经不复存在,但会虚占PID资源,因此回收是有必要的。

      ii.        线程的ID是一个long型pthread_t变量,它的范围大得多,管理方式也不一样。线程ID一般在本进程空间内作用就可以了,当然系统在管理线程时也需要记录其信息。其方式是,在内核创建一个内核态线程与之对应,也就是说每一个用户创建的线程都有一个内核态线程对应。但这种对应关系不是一对一,而是多对一的关系,也就是一个内核态线程可以对应着多个用户级线程。对于线程而言,若要主动终止需要调用pthread_exit() ,主线程需要调用pthread_join()来回收(前提是该线程没有被detached)。像线发送线程信号也是通过线程ID实现的。

    6).     资源管理方式的异同

      i.        进程本身是资源分配的基本单位,因而它的资源都是独立的,如果有多进程间的共享资源,就要用到进程间的通信方式,比如共享内存。共享数据就放在共享内存去,大家都可以访问,为保证数据写入的安全,加上信号量一同使用。一般而言,共享内存都是和信号量一起使用。消息队列则不同,由于消息的收发是原子操作,因而自动实现了互斥,单独使用就是安全的。

      ii.        线程间要使用共享资源不需要用共享内存,直接使用全局变量即可,或者malloc()动态申请内存。显得方便直接。而且互斥使用的是同一进程空间内的互斥量,所以效率上也有优势。

      iii.        实际中,为了使程序内资源充分规整,也都采用共享内存来存储核心数据。不管进程还是线程,都采用这种方式。原因之一就是,共享内存是脱离进程的资源,如果进程发生意外终止的话,共享内存可以独立存在不会被回收(是否回收由用户编程实现)。进程的空间在进程崩溃的那一刻也被系统回收了。虽然有coredump机制,但也只能是有限的弥补。共享内存在进程down之后还完整保存,这样可以拿来分析程序的故障原因。同时,运行的宝贵数据没有丢失,程序重启之后还能继续处理之前未完成的任务,这也是采用共享内存的又一大好处。

    7).     个体间辈分关系的迥异

      i.        进程的备份关系森严,在父进程没有结束前,所有的子进程都尊从父子关系,也就是说A创建了B,则A与B是父子关系,B又创建了C,则B与C也是父子关系,A与C构成爷孙关系,也就是说C是A的孙子进程。在系统上使用pstree命令打印进程树,可以清晰看到备份关系。

      ii.        多线程间的关系没有那么严格,不管是父线程还是子线程创建了新的线程,都是共享父线程的资源,所以,都可以说是父线程的子线程,也就是只存在一个父线程,其余线程都是父线程的子线程。

    8).    进程池与线程池的技术实现差别

        我们都知道,进程和线程的创建时需要时间的,并且系统所能承受的进程和线程数也是有上限的,这样一来,如果业务在运行中需要动态创建子进程或线程时,系统无法承受不能立即创建的话,必然影响业务。综上,聪明的程序员发明了一种新方法——池。在程序启动时,就预先创建一些子进程或线程,这样在需要用时直接使唤。这就是老人口中的“多生孩子多种树”。程序才开始运行,没有那么多的服务请求,必然大量的进程或线程空闲,这时候一般让他们“冬眠”,这样不耗资源,要不然一大堆孩子的口食也是个负担啊。对于进程和线程而言,方式是不一样的。另外,当你有了任务,要分配给那些孩子的时候,手段也不一样。下面就分别来解说。

      i.        进程池:

         a.        首先创建了一批进程,就得管理,也就是你得分开保存进程ID,可以用数组,也可用链表。建议用数组,这样可以实现常数内找到某个线程,而且既然做了进程池,就预先估计好了生产多少进程合适,一般也不会再动态延展。就算要动态延展,也能预估范围,提前做一个足够大的数组。不为别的,就是为了快速响应。本来做进程池的目的也是为了效率。

        b.        接下来就要让闲置进程冬眠了,可以让他们pause()挂起,也可用信号量挂起,还可以用IPC阻塞,方法很多,分析各自优缺点根据实际情况采用就是了。

        c.        然后是分配任务了,当你有任务的时候就要让他干活了。唤醒了进程,让它从哪儿开始干呢?肯定得用到进程间通信了,比如信号唤醒它,然后让它在预先指定的地方去读取任务,可以用函数指针来实现,要让它干什么,就在约定的地方设置代码段指针。这也只是告诉了它怎么干,还没说干什么(数据条件),再通过共享内存把要处理的数据设置好,这也子进程就知道怎么做了。干完之后再来一次进程间通信然后自己继续冬眠,父进程就知道孩子干完了,收割成果。

        d.        最后结束时回收子进程,向各进程发送信号唤醒,改变激活状态让其主动结束,然后逐个wait()就可以了。

      ii.        线程池:

    线程池的思想与上述类似,只是它更为轻量级,所以调度起来不用等待额外的资源。要让线程阻塞,用条件变量就是了,需要干活的时候父线程改变条件,子线程就被激活。线程间通信方式就不用赘述了,不用繁琐的通信就能达成,比起进程间效率要高一些。线程干完之后自己再改变条件,这样父线程也就知道该收割成果了。整个程序结束时,逐个改变条件并改变激活状态让子线程结束,最后逐个回收即可。

      iii.        一个进程中最多能开辟几个线程?是否是有限的?为什么能开辟那么多,怎么计算的?线程间什么资源是共享的什么是不共享的?

    答:x86 32位系统下,默认情况下,一个线程的栈要预留1M的内存空间,而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。每个线程有自己的栈空间,他们共享进程的地址空间,全局变量、文件描述符、信号句柄和当前目录状态等

  • 相关阅读:
    左偏树——可以标记合并的堆
    主席树——多棵线段树的集合
    [中山市选2011]完全平方数 ——莫比乌斯函数
    决策单调性优化dp
    [NOI2015]寿司晚宴——状压dp
    【[国家集训队]等差子序列】
    线性基——数集压缩自动机
    Java实现 蓝桥杯VIP 算法训练 筛选号码
    BSGS&EXBSGS 大手拉小手,大步小步走
    CRT&EXCRT 中国剩余定理及其扩展
  • 原文地址:https://www.cnblogs.com/BEN-LK/p/10673409.html
Copyright © 2011-2022 走看看