事件驱动与异步IO使用

事件驱动模型:
    每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求,网络服务器采用此方式。
    目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
    1. 有一个事件(消息)队列;
    2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
    3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
    4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。

让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。

在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。

在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:

  1. 程序中有许多任务,而且…
  2. 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…
  3. 在等待事件到来时,某些任务会阻塞。

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

用户空间与内核空间

Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。

Linux内核将这4G字节的空间分为两部分(虚拟空间):
	内核空间:将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用。(存放内存代码和数据)
	用户空间:将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用。(用户程序的代码和数据)

每个进程可以通过系统调用进入内核,Linux内核由系统内的所有进程共享。从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

内核空间和用户空间一般通过系统调用进行通信

Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。每个进程有各自的私有用户空间(0~3G),对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

进程切换

进程切换:为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。     因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化(很耗资源):
1. 保存处理机上下文,包括程序计数器和其他寄存器。
2. 更新PCB信息。
3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
4. 选择另一个进程执行,并更新其PCB。
5. 更新内存管理的数据结构。
6. 恢复处理机上下文。

注:进程控制块(Processing Control Block),是操作系统核心中一种数据结构,主要表示进程状态。  其作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位或与其它进程并发执行的进程。  或者说,OS是根据PCB来对并发执行的进程进行控制和管理的。PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。(数据 =====> 操作系统内核缓冲区 ====> 应用程序地址空间)

缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

IO多路复用之select、selectors详解

select(rlist, wlist, xlist, timeout=None)
参数:    1.rlist: 等待直到读准备好;   2.wlist: 等待直到写操作准备好;  3.xlist: 等待一个"exceptional condition" ;允许空序列, 但是如果3个参数都为空的列表的话, 在Unix上可以, 但在Windows上不行, 与平台相关 .当timeout参数被设定之后, 函数将blocks 知道至少一个文件描述符 is ready, 值为0 表示 a poll and never block.
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。
select优点: 目前几乎在所有的平台上支持,其良好跨平台支持。select缺点: 在于单个进程能够监视的文件描述符的数量存在最大限制,ulimit -n设置,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

 1 import select
 2 import socket
 3 import  queue
 4
 5 server = socket.socket()
 6
 7 server.bind(("localhost",9000))
 8
 9 server.listen(100)
10
11 server.setblocking(False)
12
13
14 inputs = [server]
15
16 outputs = []
17
18 msg_dic = {}
19
20 while True:
21     readable,writeable,exceptional = select.select(inputs,outputs,inputs)
22     print("readable:",readable,  # readable是活跃的那个连接句柄
23           "writeable:",writeable, # writeable下一个循环要发送数据的连接句柄
24           "exceptional:",exceptional)  # exceptional连接出错了的连接句柄
25
26     print("inputs:",inputs,  # inputs里面是所有监听的连接句柄
27           "outputs:",outputs)
28
29     for r in readable:
30         if r is server: # server活跃证明是有新连接进来了
31             conn,addr = server.accept()
32             print("来了个新连接:",addr)
33             # 实现这个客户端发数据时server端能知道就需要让select加入监测这个conn
34             inputs.append(conn)  # 新连接还没发送数据,现在接收数据程序会报错
35
36             msg_dic[conn] = queue.Queue()  # 每个新连接都初始化一个队列
37         else:
38             data = r.recv(1024).decode()
39             print("收到数据:",data)
40             msg_dic[r].put(data.upper())   # 把要发送的数据写入到队列中
41             outputs.append(r)   # r加入下次循环要发送消息的列表中
42
43     for w in writeable: # 客户端的连接列表
44         data_to_client = msg_dic[w].get()  # 获取w在队列中的数据
45         w.sendall(data_to_client.encode())  # 发送给客户端队列中的数据
46         outputs.remove(w)  # 确保下次循环的时候writeable不再返回这个已经处理完的连接
47
48     for e in exceptional:  # 某个连接出错
49         if e in outputs:   # 判断是否在outputs中
50             outputs.remove(e)
51
52         inputs.remove(e)   # 每进来一个新连接都会加入到inputs中
53
54         del msg_dic[e]  # 删除字典中的连接句柄

select运用

 1 import select
 2 import socket
 3 import queue
 4
 5
 6 server = socket.socket()
 7 server.setblocking(0)
 8
 9 server_addr = (‘localhost‘,10000)
10
11 print(‘starting up on %s port %s‘ % server_addr)
12 server.bind(server_addr)
13
14 server.listen(5)
15
16
17 inputs = [server, ] #自己也要监测呀,因为server本身也是个fd
18 outputs = []
19
20 message_queues = {}
21
22 while True:
23     print("waiting for next event...")
24
25     readable, writeable, exeptional = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里
26
27     for s in readable: #每个s就是一个socket
28
29         if s is server: #别忘记,上面我们server自己也当做一个fd放在了inputs列表里,传给了select,如果这个s是server,代表server这个fd就绪了,
30             #就是有活动了, 什么情况下它才有活动? 当然 是有新连接进来的时候 呀
31             #新连接进来了,接受这个连接
32             conn, client_addr = s.accept()
33             print("new connection from",client_addr)
34             conn.setblocking(0)
35             inputs.append(conn) #为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据, 把它放到inputs里, 下一次loop时,这个新连接
36             #就会被交给select去监听,如果这个连接的客户端发来了数据 ,那这个连接的fd在server端就会变成就续的,select就会把这个连接返回,返回到
37             #readable 列表里,然后你就可以loop readable列表,取出这个连接,开始接收数据了, 下面就是这么干 的
38
39             message_queues[conn] = queue.Queue() #接收到客户端的数据后,不立刻返回 ,暂存在队列里,以后发送
40
41         else: #s不是server的话,那就只能是一个 与客户端建立的连接的fd了
42             #客户端的数据过来了,在这接收
43             data = s.recv(1024)
44             if data:
45                 print("收到来自[%s]的数据:" % s.getpeername()[0], data)
46                 message_queues[s].put(data) #收到的数据先放到queue里,一会返回给客户端
47                 if s not in outputs:
48                     outputs.append(s) #为了不影响处理与其它客户端的连接 , 这里不立刻返回数据给客户端
49
50             else:#如果收不到data代表什么呢? 代表客户端断开了呀
51                 print("客户端断开了",s)
52
53                 if s in outputs:
54                     outputs.remove(s) #清理已断开的连接
55
56                 inputs.remove(s) #清理已断开的连接
57
58                 del message_queues[s] ##清理已断开的连接
59
60     for s in writeable:
61         try :
62             next_msg = message_queues[s].get_nowait()
63
64         except queue.Empty:
65             print("client [%s]" %s.getpeername()[0], "queue is empty..")
66             outputs.remove(s)
67
68         else:
69             print("sending msg to [%s]"%s.getpeername()[0], next_msg)
70             s.send(next_msg.upper())
71
72     for s in exeptional:
73         print("handling exception for ",s.getpeername())
74         inputs.remove(s)
75         if s in outputs:
76             outputs.remove(s)
77         s.close()
78
79         del message_queues[s]

select实例

  1 import socket, logging
  2 import select, errno
  3
  4 logger = logging.getLogger("network-server")
  5
  6 def InitLog():
  7     logger.setLevel(logging.DEBUG)
  8
  9     fh = logging.FileHandler("network-server.log")
 10     fh.setLevel(logging.DEBUG)
 11     ch = logging.StreamHandler()
 12     ch.setLevel(logging.ERROR)
 13
 14     formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
 15     ch.setFormatter(formatter)
 16     fh.setFormatter(formatter)
 17
 18     logger.addHandler(fh)
 19     logger.addHandler(ch)
 20
 21
 22 if __name__ == "__main__":
 23     InitLog()
 24
 25     try:
 26         # 创建 TCP socket 作为监听 socket
 27         listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
 28     except socket.error as msg:
 29         logger.error("create socket failed")
 30
 31     try:
 32         # 设置 SO_REUSEADDR 选项
 33         listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 34     except socket.error as msg:
 35         logger.error("setsocketopt SO_REUSEADDR failed")
 36
 37     try:
 38         # 进行 bind -- 此处未指定 ip 地址,即 bind 了全部网卡 ip 上
 39         listen_fd.bind((‘‘, 2003))
 40     except socket.error as msg:
 41         logger.error("bind failed")
 42
 43     try:
 44         # 设置 listen 的 backlog 数
 45         listen_fd.listen(10)
 46     except socket.error as  msg:
 47         logger.error(msg)
 48
 49     try:
 50         # 创建 epoll 句柄
 51         epoll_fd = select.epoll()
 52         # 向 epoll 句柄中注册 监听 socket 的 可读 事件
 53         epoll_fd.register(listen_fd.fileno(), select.EPOLLIN)
 54     except select.error as  msg:
 55         logger.error(msg)
 56
 57     connections = {}
 58     addresses = {}
 59     datalist = {}
 60     while True:
 61         # epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
 62         epoll_list = epoll_fd.poll()
 63
 64         for fd, events in epoll_list:
 65             # 若为监听 fd 被激活
 66             if fd == listen_fd.fileno():
 67                 # 进行 accept -- 获得连接上来 client 的 ip 和 port,以及 socket 句柄
 68                 conn, addr = listen_fd.accept()
 69                 logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
 70                 # 将连接 socket 设置为 非阻塞
 71                 conn.setblocking(0)
 72                 # 向 epoll 句柄中注册 连接 socket 的 可读 事件
 73                 epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
 74                 # 将 conn 和 addr 信息分别保存起来
 75                 connections[conn.fileno()] = conn
 76                 addresses[conn.fileno()] = addr
 77             elif select.EPOLLIN & events:
 78                 # 有 可读 事件激活
 79                 datas = ‘‘
 80                 while True:
 81                     try:
 82                         # 从激活 fd 上 recv 10 字节数据
 83                         data = connections[fd].recv(10)
 84                         # 若当前没有接收到数据,并且之前的累计数据也没有
 85                         if not data and not datas:
 86                             # 从 epoll 句柄中移除该 连接 fd
 87                             epoll_fd.unregister(fd)
 88                             # server 侧主动关闭该 连接 fd
 89                             connections[fd].close()
 90                             logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
 91                             break
 92                         else:
 93                             # 将接收到的数据拼接保存在 datas 中
 94                             datas += data
 95                     except socket.error as  msg:
 96                         # 在 非阻塞 socket 上进行 recv 需要处理 读穿 的情况
 97                         # 这里实际上是利用 读穿 出 异常 的方式跳到这里进行后续处理
 98                         if msg.errno == errno.EAGAIN:
 99                             logger.debug("%s receive %s" % (fd, datas))
100                             # 将已接收数据保存起来
101                             datalist[fd] = datas
102                             # 更新 epoll 句柄中连接d 注册事件为 可写
103                             epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT)
104                             break
105                         else:
106                             # 出错处理
107                             epoll_fd.unregister(fd)
108                             connections[fd].close()
109                             logger.error(msg)
110                             break
111             elif select.EPOLLHUP & events:
112                 # 有 HUP 事件激活
113                 epoll_fd.unregister(fd)
114                 connections[fd].close()
115                 logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
116             elif select.EPOLLOUT & events:
117                 # 有 可写 事件激活
118                 sendLen = 0
119                 # 通过 while 循环确保将 buf 中的数据全部发送出去
120                 while True:
121                     # 将之前收到的数据发回 client -- 通过 sendLen 来控制发送位置
122                     sendLen += connections[fd].send(datalist[fd][sendLen:])
123                     # 在全部发送完毕后退出 while 循环
124                     if sendLen == len(datalist[fd]):
125                         break
126                 # 更新 epoll 句柄中连接 fd 注册事件为 可读
127                 epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET)
128             else:
129                 # 其他 epoll 事件不进行处理
130                 continue

epoll实例

selectors模块

selectors模块是select多路复用封装的加强版,在windows中会选择select,在linux中会选择epoll
  • win: select
  • linux : select poll epoll

通常是用户空间创建fd,然后copy到内核空间,如果是开fd的数量多,select的的效率低

  1 import selectors
  2 import socket
  3
  4 sock = socket.socket()
  5 sock.bind(("0.0.0.0",9999))
  6
  7 sock.listen(1000)   # 一定要加listen,不然无法连接端口
  8
  9 sock.setblocking(False)  # 非阻塞模式
 10
 11 sel = selectors.DefaultSelector()   # 实例化一个对象,会根据不同平台自动设置优先级
 12 # epoll|kqueue|devpoll > poll > select.  所以Linux系统会自动设置成epoll  win 自动设置成select
 13
 14
 15 # 第一步
 16 def accept(sock):  # mask 是没有用的
 17     conn,addr = sock.accept()
 18     print("accepted:",conn,"from:",addr)
 19     """
 20     conn:
 21         <socket.socket fd=540,
 22          family=AddressFamily.AF_INET,
 23          type=SocketKind.SOCK_STREAM,
 24          proto=0, laddr=(‘127.0.0.1‘, 9999),
 25          raddr=(‘127.0.0.1‘, 58702)>
 26
 27     addr: (‘127.0.0.1‘, 58702)
 28     """
 29     conn.setblocking(False)
 30
 31     sel.register(conn,selectors.EVENT_READ,read)  # 注册了,把conn与read函数绑定
 32
 33
 34 # 第三步
 35 def read(conn):
 36     data = conn.recv(1024)
 37     if data:
 38         """
 39         >>> s = "hello, world."
 40         >>> str(s)
 41         ‘hello, world.‘
 42         >>> repr(s)
 43         "‘hello, world.‘"
 44
 45         """
 46         print("echoing",repr(data),"to",conn)  # repr()与str()都是转为字符,repr()输出对python比较有好,str()输出对用户比较友好
 47         """
 48         data: b‘lls‘
 49
 50         conn:
 51             <socket.socket fd=540,
 52                 family=AddressFamily.AF_INET,
 53                 type=SocketKind.SOCK_STREAM,
 54                 proto=0, laddr=(‘127.0.0.1‘, 9999),
 55                 raddr=(‘127.0.0.1‘, 58702)
 56             >
 57
 58         """
 59         conn.send(data)
 60     else:
 61         print("closing",conn)
 62         sel.unregister(conn)  # 解除注册
 63         conn.close()
 64
 65 sel.register(sock,selectors.EVENT_READ,accept)  # 注册,但是没有监听accept函数,把sock和accept绑定
 66
 67 # 监听
 68 while True:
 69     events = sel.select()  # events返回的是活跃的socket句柄
 70     """
 71     events:
 72     [
 73         (
 74             SelectorKey(
 75                 fileobj=<socket.socket fd=520, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘0.0.0.0‘, 9999)>,
 76                 fd=520, events=1,
 77                 data=<function accept at 0x05575ED0>
 78                 ),
 79             1
 80         )
 81     ]
 82
 83     """
 84
 85     """
 86     events:
 87     [
 88         (
 89             SelectorKey(
 90                 fileobj=<socket.socket fd=540, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM,proto=0, laddr=(‘127.0.0.1‘, 9999), raddr=(‘127.0.0.1‘, 58702)>,
 91                 fd=540, events=1,
 92                 data=<function read at 0x05575F18>
 93                 ),
 94             1
 95         )
 96     ]
 97
 98     """
 99
100     for key,mask in events:   # events是列表,需要遍历
101         """
102         key:
103             SelectorKey(
104                 fileobj=<socket.socket fd=520, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘0.0.0.0‘, 9999)>,
105                 fd=520, events=1,
106                 data=<function accept at 0x05575ED0>
107             )
108
109         mask: 1
110         """
111
112         """
113         key:
114             SelectorKey(
115                 fileobj=<socket.socket fd=540, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM,proto=0, laddr=(‘127.0.0.1‘, 9999), raddr=(‘127.0.0.1‘, 58702)>,
116                 fd=540, events=1,
117                 data=<function read at 0x05575F18>
118                 ),
119
120         mask: 1
121         """
122
123         callback = key.data  # key.data是accept函数内存地址
124         """
125         key.data: <function accept at 0x05575ED0>
126
127         callback: <function accept at 0x05575ED0>
128         """
129
130         """
131         key.data: <function read at 0x05575F18>
132
133         callback: <function read at 0x05575F18>
134         """
135
136         callback(key.fileobj,mask)  # key.fileobj是socket句柄
137         """
138         key.fileobj:
139             <
140                 socket.socket fd=520,
141                 family=AddressFamily.AF_INET,
142                 type=SocketKind.SOCK_STREAM,
143                 proto=0,
144                 laddr=(‘0.0.0.0‘, 9999)
145             >
146
147         mask: 1
148         """
149
150         """
151         key.fileobj:
152             <
153                 socket.socket fd=540,
154                 family=AddressFamily.AF_INET,
155                 type=SocketKind.SOCK_STREAM,
156                 proto=0,
157                 laddr=(‘127.0.0.1‘, 9999),
158                 raddr=(‘127.0.0.1‘, 58702)
159             >
160
161         mask: 1
162         """

selectors实例分析

socket实例化 ---->  selectors注册 ---> while 循环  ---> accept ----> while 循环 ----> read

原文地址:https://www.cnblogs.com/sshcy/p/8315412.html

时间: 2024-08-08 09:15:47

事件驱动与异步IO使用的相关文章

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selectors模块 # Python队列/RabbitMQ队列

1 # 进程/线程/协程 2 # IO:同步/异步/阻塞/非阻塞 3 # greenlet gevent 4 # 事件驱动与异步IO 5 # Select\Poll\Epoll异步IO 以及selectors模块 6 # Python队列/RabbitMQ队列 7 8 ############################################################################################## 9 1.什么是进程?进程和程序之间有什么

python2.0_s12_day9_事件驱动编程&amp;异步IO

论事件驱动与异步IO 事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定.它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理.另外两种常见的编程范式是(单线程)同步以及多线程编程. 让我们用例子来比较和对比一下单线程.多线程以及事件驱动编程模型.下图展示了随着时间的推移,这三种模式下程序所做的工作.这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身.阻塞在I/O操作上所花费的时间已经用灰色框标示出来了. 在单线程同步模型中,任务按照顺序执行.如果某个

难得二逼的协程,事件驱动和异步IO

先来回顾一下多线程和多进程把.多线程像是在一个国家内,由A点往B点搬运东西,一条线程就是一条路,多条线程就是开启多条路,然后每条路上可以运输东西.多进程就像多个国家,每个国家里面在执行自己的事情. 然后轮到今天的主角:协程出场 1.携程 corotine, 是一种用户态的轻量级线程,被称为微线程.是自己控制的,cpu不知道其存在.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈. 协程的好处:? 无需线程上下文切换的开销? 无需原子操作锁定及同步的

论事件驱动与异步IO

通常我们写服务器模型,有以下几种模型: 每收到一个请求,创建一个新的进程,来处理该请求 每收到一个请求,创建一个新的线程,来处理该请求 每收到一个请求,放入到一个事件中,让主程序通过非阻塞I/0方式来处理请求 以上几种方式,各有千秋: 第1种方式,由于创建新的进程开销比较大,所以会导致服务器性能比较低,但实现比较简单 第2种方式,由于要涉及到线程的同步,有可能会面临死锁等问题 第3种方式,在写应用程序代码时,逻辑比前面两种都复杂. 综合考虑因素,一般普遍认为第三种是大多数网络服务器采用的方式.

异步IO\数据库\队列\缓存

本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SSH Twsited网络框架 引子 到目前为止,我们已经学了网络并发编程的2个套路, 多进程,多线程,这哥俩的优势和劣势都非常的明显,我们一起来回顾下 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度

python的协程和异步io【select|poll|epoll】

协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来后,恢复之前保存的寄存器的上下文关系,因此协程能保留上一次调用的状态,每次过程重入的时候,就相当于进入上一次调用的状态 协程一定在单线程中,协程的切换是在线程中切换,和单个线程在cpu之间不停的切换是一样的但是线程切换是cpu控制的,而协程的切换是用户控制的,操作系统根本无感知:协程的切换比线程的切换速

python 协程, 异步IO Select 和 selectors 模块 多并发演示

主要内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 selectors 模块 多并发演示 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开

Python开发【第九篇】:协程、异步IO

协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈.因此,协程能保留上一次调用的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法,进入上一次离开时所处逻辑流的位置. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返

Python全栈开发-Day10-进程/协程/异步IO/IO多路复用

本节内容 多进程multiprocessing 进程间的通讯 协程 论事件驱动与异步IO Select\Poll\Epoll--IO多路复用   1.多进程multiprocessing Python的线程用的是操作系统的原生线程,同样python的进程用的是操作系统的原生进程. 多进程之间没有锁的概念,多进程之间数据不能互相访问,所以不存在互斥锁.GIL问题又是仅仅出现在多线程中. 所以如果我们启动8个进程,每个进程有一个主线程,即8个线程,分别运行在8个CPU上,就可以充分利用多核的优势了.