并行 VS 并发
并发:1->N
并行:N->N—> 同一时刻多个任务同时在运行
线程是并发,进程是并行;
多进程
1.一个运行的程序(代码)就是一个进程
2.进程是系统资源分配的最小单位
3.进程拥有自己的独立内存空间,所以进程间数据不共享,开销大
4.进程间通信是通过Queue。Pipes等实现的
5.密集型cpu任务,需要充分使用多核cpu资源(服务器,大量的并行计算)的时候用多进程
缺点:
多个进程间通讯成本高 切换开销大
优势:
最大的优点就是稳定性高, 因为一个子进程奔溃了,不会影响主进程和其他子进程
多线程
1.线程是CPU调度执行的最小单位(由操作系统调度),不能独立执行,依赖于进程,一个进程可以有多个线程
2.线程之间共享内存单位,从而提高了程序运行的效率
3.多线程模型复杂,容易发生冲突 必须用锁加以隔离,同时又要小心死锁的发生
4.密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适
缺点:
请求返回的数据是无序的,
同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发
多线程竞争:
线程是非独立的,同一个进程里线程是数据共享的。当各个线程访问数据资源时会出现竞争状态即:
数据几乎同步会被多个线程占用,造成数据混乱,线程不安全
竞争解决方案:锁
锁的好处:
确保了某段代码(共享数据资源)只能由一个线程冲头到尾完整的执行
锁的坏处:
阻止了多个线程并发执行。包含了某段锁的代码 实际上只能以单线程执行了
线程同步:
- Lock:互斥锁,只能有一个线程获取,获取该锁的线程才能执行,否则阻塞;
1. 创建锁:Lock()
1. 获得锁:acquire([_blocking_])
1. 释放锁:release()
- RLock:递归锁,也称可重入锁,已经获得该锁的线程可以继续多次获得该锁,而不会被阻塞,释放的次数必须和获取的次数相同才会真正释放该锁;(由于当前线程获得锁之后,在释放锁之前有可能再次获取锁导致死锁。python引入了重入锁。)
- 创建锁:RLock()
- 获得锁:acquire([_blocking_])
- 释放锁:release()
- Condition:条件变量,使得一个线程等待另一个线程满足特定条件,比如改变状态或某个值。然后会主动通知另一个线程,并主动放弃锁;
- Semaphore:信号锁。为线程间共享的有限资源提供一个”计数器”,如果没有可用资源则会被阻塞;
- Event:事件锁,任意数量的线程等待某个事件的发生,在该事件发生后所有线程被激活;
- Timer:一种计时器(其用法比较简单,不算同步机制暂不介绍)
协程
1.是一种用户态的轻量级线程,它的调度完全由用户控制
2.由于有自己的寄存器上下文和栈协程调度时候,将寄存器上下文和栈保存到其他地方,
在切换回来的时候恢复到先前保存寄存器上下文和栈,因为没有进过内核切换开销,
可以不加锁的访问,所以速度很快
3.返回的数据是有序的
缺点:
单线程执行,处理密集CPU和本地磁盘io的时候 性能较低
处理网络i/o性能还是比较高的
优势:
理论数量上可以是无限个,而且没有线程之间的切换动作 执行效率比小城高 它不需要锁机制 因为说有的协程都是在一个线程种
守护进程
守护进程(本身就是一个子进程):会在主进程代码运行结束的情况下,立即挂掉
为什么要用?
需要改进程的生命周期设置程主进程同步的时候才去使用
守护线程
守护线程会在该进程内所有非守护线程全部运行完毕后,守护线程才会挂
守护线程守护的是:当前进程内所有的子线程!
同步/异步/阻塞/非阻塞
1.多个任务之间有先后顺序执行,一个执行完下个才能执行。
2.多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!
3.如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
4.如果不会卡住,可以继续执行,就是说非阻塞的。
同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。
僵尸进程和孤儿进程
僵尸进程:
进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。
避免僵尸进程的方法:
1.fork 两次用孙子进程去完成子进程的任务
2.用wait()函数使父进程阻塞
3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞
孤儿进程:
父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进程号为1)所收养,并由init 进程对他们完成状态收集工作