进程
进程和程序的区别:
编写完毕的代码,在没有运行的时候,称之为程序
正在运行着的代码,就成为进程
进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的。
进程——资源分配的基本单位
线程——是CPU调度的最小单位.
fork子进程(linux上,不能跨平台在windows)
res = os.fork() 它有两个返回值,是两次分别返回,也就是一次返回一个,不是一次返回两个,主进程的返回值是大于0的,而且每次和每次的值是不一样的,而子进程的返回值是等于0的。这样操作系统就能区分主进程和子进程了
1、父子进程执行的先后顺序 ,没有规律,由操作系统决定(调度算法)
2、getpid返回当前进程标识,getppid返回父进程标识,进程标识也就是进程识别码。
3、
总结:父进程中fork的返回值,就是刚刚创建出来的子进程的id。
- fork 不会等待子进程执行完在退出
总结:父进程中fork的返回值,就是刚刚创建出来的子进程的id。 - 全局变量在多个进程中不共享
多进程中,每个进程中所有数据(包括全局变量)都各有拥有一份,互不影响。进程可以完成多任务,但是,进程与进程之间是独立的,进程和进程之间想要实现通信,需要网络。 - 多个fork
Process创建子进程(跨平台)
1、创建一个Process实例,用start()方法启动,进程所要执行的代码是通过target这个参数指定的
- 主进程等待Process子进程先结束后才结束
3、Process强调
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
(正常情况下是主进程先执行完自己的代码再去等待子进程)
join([timeout]):是否等待进程实例执行结束,或等待多少秒
4、通过类创建子进程
这个类必须继承process这个类,还需要在这个类中重写一个run方法,将来在开启这个进程的时候也就是对象.start的时候一定会去调用继承的process这个类的start方法,在这个方法中一定去调用了run方法的。
进程池Pool
非阻塞(并行执行):apply_async(func[, args[, kwds]])
阻塞(等待上一个进程退出才能执行下一个):apply
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出,必须在close或terminate之后使用;
优点:
进程池可以避免创建和销毁进程本身的额外开销,提高进程的运行效率和进程对象的复用。
没有任务时子进程都睡眠在该工作队列上,当有新的任务到来时,主进程将任务添加到工作队列中。
多种方式的比较
孤儿进程、僵尸进程和守护进程
1.孤儿进程
如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,这是子进程的父进程就是init进程(1号进程).其实还是很好理解的
fork
2、僵尸进程
如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程.僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态,一旦父进程得到想要的信息,僵尸进程就会结束.
怎么才能避免僵尸进程?
子进程退出的同时会给父进程发送一个SIGCHILD信号。
Signal()函数的第一个函数是Linux支持的信号,第二个参数是对信号的操作 ,是系统默认还是忽略或捕获
signal(SIGCHLD,SIG_IGN)是选择对子程序终止信号选择忽略,这是僵尸进程就是交个内核自己处理,并不会产生僵尸进程.
3、守护进程
守护进程就是在后台运行,不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务.习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的.
ps -aux | grep ssh
多任务或者单任务分为两种:
IO密集型的任务 (适合多线程,协程)
- 涉及到网络、磁盘IO的任务都是IO密集型任务。
有阻塞的状态,就是不会一直运行CPU(中间就一个等待状态,就告诉CPU 等待状态,这个就叫IO密集型),例如:sleep 状态等
2、特点:CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。
3、对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
4、实现单核CPU上运行单进程却能实现多任务的并发。该种任务Python(执行效率相对较低)比较适合。
计算密集型的任务(多进程)
1、特点:要进行大量的计算,消耗CPU资源。比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
2、计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
3、计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
进程间通信——Queue队列
进程与进程之间它们默认是没有关联的,它不能完成一个数据的共享,解决的其中之一方法就是队列( 其他方法:命名管道,无名管道,共享内存,网络socket)。
默认进程池大小等于CPU核心数
线程
- 主线程会等待所有的子线程结束后才结束,线程的执行顺序,由操作系统负责调度
- 线程共享全局变量 线程里面的局部变量各是各的。
200 万 bug的出现原因:线程争抢CPU资源(单核cpu)
问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。
3、进程线程比较:
- 定义的不同
进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. - 区别
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线程不能够独立执行,必须依存在进程中 - 优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。
4、避免全局变量被修改
全局变量:g_flag
互斥锁 :互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
锁的好处:
确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
避免死锁
设置加锁顺序 设置加锁时限 死锁检测
协程
- 协程是在一个进程里面或是一个线程里面,将任务分成了多份,也是完成多任务、并发的一种方式。
- 协程的底层就是生成器
协程其实可以认为是比线程更小的执行单元,自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。
3、协程和线程差异
线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。
4、优点
- 无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
协程的缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。