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个线程,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。每个线程有自己的栈空间,他们共享进程的地址空间,全局变量、文件描述符、信号句柄和当前目录状态等
Liunx系统下的进程与线程的更多相关文章
- Linux下的进程与线程(二)—— 信号
Linux进程之间的通信: 本文主要讨论信号问题. 在Linux下的进程与线程(一)中提到,调度器可以用中断的方式调度进程. 然而,进程是怎么知道自己需要被调度了呢?是内核通过向进程发送信号,进程才得 ...
- Supervisor-类unix系统下的进程控制工具
如果你的英文足够好,请看官网的文档:http://supervisord.org/introduction.html 简介: Supervisor 类unix系统下的进程控制工具. 特性: 1.配置简 ...
- Linux下查看进程和线程
在linux中查看线程数的三种方法 1.top -H 手册中说:-H : Threads toggle 加上这个选项启动top,top一行显示一个线程.否则,它一行显示一个进程. 2.ps xH 手册 ...
- Linux下的进程与线程(一)—— 进程概览
进程是操作系统分配资源的基本单位.线程是操作系统进行运行和调度的基本单位. 进程之间可以切换,以便轮流占用CPU,实现并发.一般进程运行在用户模式下,只能执行指令集中的部分指令. 当进程进行上下文切换 ...
- linux下的进程,子进程,线程
1.相同点:(a)二者都具有ID,一组寄存器,状态,优先级以及所要遵循的调度策略.(b) 每个进程都有一个进程控制块,线程也拥有一个线程控制块.(c) 线程和子进程共享父进程中的资源:线程和子进程独立 ...
- JAVA下查看进程中线程占用CPU较高的代码
执行以下代码获得进程的线程.并找到CPU占用较高的线程ID ps -mp 16935 -o THREAD,tid,time 执行以下代码,通过线程ID获得16进制的线程标识 printf &quo ...
- linxu下查看进程的线程方法;如何知道某个进程或者线程运行在哪个CPU上?
1.top -H -p <pid> ; top -H 在top命令后,按H键:或者top -H 2.ps -T -p <pid> “-T”选项可以开启线程查看 3.htop, ...
- 06.系统编程-3.进程VS线程比较
1.定义的不同 ==进程是系统进行资源分配和调度的一个独立单位.== ==线程是进程的一个实体,是CPU调度和分派的基本单位==,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只 ...
- liunx系统下调整Swap分区大小
作者:邓聪聪 添加swap交换空间的步骤如下:第一步:确保系统中有足够的空间来用做swap交换空间,准备在一个独立的文件系统中添加一个swap交换文件,在/tmp中添加1G的swap交换文件第二步:添 ...
随机推荐
- webpack-dev-server运行时报错
注意:webpack 在进行打包时,利用webpack-dev-server进行打包启动时,应该对webpack-dev-server进行全局安装,不然也会报错,( cnpm install webp ...
- 史上最全PMP备考考点全攻略(上篇-五大过程组,附赠资料)
一.这可能是一篇史上最全的PMP备考考点全梳理文章 写在前面,这可能是史上最全的PMBOK考点全书考点梳理,由PMP备考自律营呕心沥血整理,内容较长,分为上下篇,绝对值得所有正在备考PMP的学员收藏! ...
- 经典面试题-python函数之默认参数
1.可变的默认参数----list 示例: def add(a, mylist=[]): # print(id(mylist)) mylist.append(a) return mylist pri ...
- windows下网络编程TCP
转载 sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW): 基于TCP的socket编程 服务器端编程的步骤 ...
- leveldb实现原理
LevelDb日知录之一:LevelDb 101 说起LevelDb也许您不清楚,但是如果作为IT工程师,不知道下面两位大神级别的工程师,那您的领导估计会Hold不住了:Jeff Dean和Sanja ...
- Unity Technologies-提供全面的技术支持服务
Unity Technologies-提供全面的技术支持服务 在收费服务是由Unity大华区面向研发企业推出的一项技术支持服务,以全中文的方式进行,为研发团队解答在使用Unity引擎过程中遇到的各类问 ...
- 深入剖析Kubernetes学习笔记:容器基础(05-06)
05 :从进程说起 1.容器本身没有价值,有价值的是"容器编排" 2.什么是进程? 一旦"程序"被执行起来,它就从磁盘上的二进制文件,变成 1.计算机内存中的数 ...
- 图解TCP/IP
序言 ----
- 关于接口(Interface)
接口,其实是指类之间约定的协议,可以包含方法.属性.事件和索引: 接口成员不允许使用访问修饰符号(public.private.protected.internal),所有的接口成员都是公共的. 接口 ...
- MYSQL的学习
启动MYSQL :net start mysql或者手动启动,输入密码:mysql -u root -p 先创建数据库在创建表格,创建数据库:create databsse 数据库名称,创建表格:cr ...