1. 多进程:
优势:可以充分地利用cpu的多核,实现真正的并行(尤其是在python这种带有GIL锁的语言里),每个进程处理各自的请求,实现并发io处理。
缺点:
- 1.1 网络应用是io密集型的,多cpu处理并没有带来多大的优势;
- 1.2 多进程中,各个进程不方便互相通信,为每一条请求创建一个进程开销太大,当有成千上万条请求的时候极容易拖垮整个系统
2. 多线程
优势:各个线程都在同一个进程中进行处理,线程之间很方便就能共享一些数据和资源,线程的开销小于进程。对网络io处理很有用。
缺点:
- 1.1 在python这类语言里,传统的多线程因为全局解释锁的缘故并不能利用到多核.
- 1.2 Python的线程切换需要消耗较多资源。
- 1.3 多线程都在同一个进程进行处理,这时候对数据的一致性和执行顺序都要保证被正确处理,往往需要通过事件或锁机制来保证这个步骤,编程难度变高。
多线程都在同一个进程进行处理,这时候对数据的一致性和执行顺序都要保证被正确处理,往往需要通过事件或锁机制来保证这个步骤,编程难度变高。
3. 异步回调
优势: 能避免一些多线程中的坑
缺点:捕获异常也比较繁琐,不便于人类开发调试。
4. 协程
用的是同步的语法,通过保存上下文的状态,实现在各个协程中自由的切换而保证程序的状态
[主动调用/退出,状态保存,避免cpu上下文切换]
即协同运行的例程,它是比是线程(thread)更细量级的用户态线程,特点是允许用户的主动调用和主动退出,
挂起当前的例程然后返回或去执行其他任务,接着返回原来停下的点继续执行(用yield语句)
感谢操作系统为我们做的工作,因为它具有getcontext和swapcontext这些特性,通过系统调用,
我们可以把上下文和状态保存起来,切换到其他的上下文,这些特性为coroutine的实现提供了底层的基础。
操作系统的Interrupts和Traps机制则为这种实现提供了可能性
5. gevent 见wsgi.md
6. 同步与异步 and 阻塞IO与非阻塞IO
同步IO与异步IO:是针对应用程序与内核的交互而言的。
同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。
异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成
确定是否为异步,要看操作系统是否将用户数据主动的拷贝到用户(服务器)空间去供用户使用,
而不是用户(服务器)主动监听
程序vs内核
同步与异步 and 阻塞IO与非阻塞IO
同步阻塞:
同一个线程在IO时一直阻塞,直到读取数据成功,把数据从核心空间拷贝到用户空间
同步非阻塞:
同一个线程发起IO后,立即获得返回,后面定期轮询数据读取情况,发现数据读取成功,把数据从核心空间拷贝到用户空间
异步非阻塞:
一个线程发起IO后,立即返回,由[创建的另外的线程]发现数据读取成功,把数据从核心空间拷贝到用户空间。
阻塞IO与非阻塞IO:简单理解为需要做一件事能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞了,
否则就可以理解为非阻塞。
阻塞:一直处于睡眠状态等待被唤醒,这种消耗CPU资源少。
非阻塞:
非阻塞忙轮询: 一直询问是否可用,占用资源多。会出现CPU空转。
IO多路复用:select,poll,epoll,解决空转问题。
SELECT: O(n)
同时观察多个io事件,如果没有I/O事件产生,我们的程序就会阻塞在select处.
有io事件发生,无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
EPOLL: epoll之会把哪个流发生了怎样的I/O事件通知我们,复杂度降低到了O(1)
缓冲区非空,缓冲区非满
7. select 和 epoll 模型
select 模型 [无差别对待]
- select只能监听有限个socket,主要因为__FD_SETSIZE限制?1024个???
- select 会轮训一遍socket(其实就轮询到max_fd不会轮询完 __FD_SETSIZE)
但不会一直轮询所有的socket,如果一次轮询之后没有结果,会进入休眠,节约cpu资源
问题:
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 --O(n)
看哪个就绪:去唤醒
(3)select支持的文件描述符数量太小了,默认是1024
epoll 模型 [有状态]
网络请求队列处理,预先分配好了,节约资源。
epoll和 select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。
而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,
epoll_create是[创建]一个epoll句柄;
epoll_ctl是[注册]要监听的事件类型;
epoll_wait则是等待事件的产生。[监听]
(1) 对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),
会把(epoll相关的?)所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
(2) 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,
而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,
就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd
(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。--O(1)
(3) 对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,
举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
8. 线程同步
解决同步访问资源问题
原子操作、锁、可重入锁、信号量、时间、条件、队列
8.1
[todo!!!!!!!!!!!!!!!!!!!!]
9. 进程同步
共享内存
管道:允许一个进程和另一个与它有共同祖先的进程之间进行通信。
信号 Signal
信号量:为进程间以及同一进程不同线程之间的同步手段
网友评论