day 10 异步IO,select模块

'''
1.协程是一种用户态的轻量级线程
2.协程有自己的register上下文和栈,协程调度切换时,将寄存器的上下文和栈保存到其他地方,在切回来的时候,回复先前保存的寄存器上下文和栈
因此:协程能保留上一次调用的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法,进入上一次离开时所处逻辑流的位置
advantage:
1.无需线程上下文切换的开销
2.无需atomic operation锁定及同步的开销
atomic operation原子操作一旦开始,就一直运行到结束,中间不会有任何切换到另外线程的操作,原子操作可以是一个或多个步骤,但是顺序不会被打乱
3.方便切换控制流,简化变成模型
4.高并发+高扩展+低成本:一个cpu可以支持上万个协程

disadvantage:
1.无法利用多核资源
2.进行阻塞blocking操作如(IO)时会阻塞掉整个程序

协程的直接条件:
1.必须在一个单线程里实现并发
2.修改共享数据不需要加锁
3.用户程序里自己保存多个控制流的上下文栈
4.一个协程遇到IO操作自动切换到其他协程
'''
#greenlet ,gevent是第三方库的协程
#_*_ coding:utf-8 _*_
from greenlet import greenlet

def test():
    print('ab')
    gr2.switch()
    print('cd')
    gr2.switch()

def test1():
    print(12)
    gr1.switch()
    print(34)
    gr1.switch()

if __name__ == "__main__":
    gr1 = greenlet(test)
    gr2 = greenlet(test1)
    gr1.switch()
    '''
    ab
    12
    cd
    34
    '''
    #test()
    '''
    ab
12
ab
34
cd
cd
    '''

'''
Gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,
gevent中的主要模式是greenlet,
greenlet全部运行在主程序操作系统进程的内部,但他们被协作式的调用
'''
#greenlet ,gevent是第三方库的协程
#* coding:utf-8 *
import gevent

def test():
print('ab')
gevent.sleep(2)
print('cd')

def test1():
print(12)
gevent.sleep(15)
print(34)

if name == "main":
gevent.joinall( [gevent.spawn(test),
gevent.spawn(test1)])

'''
先直接运行test.print('ab')
         test1.print(12)
然后过2秒运行test.print('cd')
然后过(15-2)秒运行test1.print(34)
'''

    '''

该程序的作用是,把task函数封装在greenlet内部线程的gevent,spawn里
初始化的greenlet列表存在数组threads里,然后把数组传值到gevent.joinall()里开始阻塞当前流程,等待执行完这个语句后才开始主线程执行
'''
#* coding:utf-8 *
import gevent

def task(pid):
gevent.sleep(0.5)
print('task %s done' % pid)

def synchronous():
for i in range(1,10):
task(i)

def asynchronous():
#threads = [gevent.spawn(task,i) for i in range(10)] #生成10个task(i)列表封装到gevent.spawn里
threads = []
for i in range(10):
threads.append(gevent.spawn(task,i))

gevent.joinall(threads)

if name == "main":
print('同步:') #也就是直接运行
synchronous()
print('异步:') #多线程运行
asynchronous()

'''
同步下就是10次循环执行task(),异步就是多个线程同时执行task()
'''
    '''

这几章需要补充视频
'''
#* coding:utf-8 *
import gevent
from gevent import monkey;monkey.patch_all()
from urllib.request import urlopen
import time
def f(url):
print('GET: %s' % url)
try:
resp = urlopen(url)
data = resp.read()
#time.sleep(2)
print('%d bytes received from %s.' % (len(data),url))
except Exception as e:
print(url,end='')
print(e)

if name == "main":
gevent.joinall([
gevent.spawn(f,'http://192.168.0.19:8080/realestate/app/framework/login/main/'),
gevent.spawn(f, 'http://www.baidu.com'),
gevent.spawn(f, 'ftp://192.168.0.20')

])

# f( 'http://192.168.0.19:8080/realestate/app/framework/login/main/'),
# f(),
# f('ftp://192.168.0.20')
'''
同步下就是10次循环执行task(),异步就是多个线程同时执行task()
'''
    '''

这几章需要补充视频
'''
#* coding:utf-8 *
import gevent
from gevent import monkey;monkey.patch_all()
from urllib.request import urlopen
import time
def f(url):
print('GET: %s' % url)
resp = urlopen(url)
data = resp.read()
time.sleep(2)
print('%d bytes received from %s.' % (len(data),url))

if name == "main":

gevent.joinall([

#     gevent.spawn(f,'http://192.168.0.19:8080/realestate/app/framework/login/main/'),
#     gevent.spawn(f, 'http://192.168.0.15/sywshtbaweb/checklogin.aspx'),
#     gevent.spawn(f, 'ftp://192.168.0.20')
#
# ])
f( 'http://192.168.0.19:8080/realestate/app/framework/login/main/'),
f('http://192.168.0.15/sywshtbaweb/checklogin.aspx'),
f('ftp://192.168.0.20')
'''
同步下就是10次循环执行task(),异步就是多个线程同时执行task()
'''

    '''

单线程 多 线 程 事件驱动模型
执行任务1 执行任务1 执行任务2 执行任务3 执行任务1
等待IO 等待IO 等待IO 等待IO 任务1IO空闲时执行任务2
执行任务1 执行任务1 执行任务2 执行任务3 任务2IO空闲时执行任务3
等待IO 等待IO 等待IO 等待IO IO结束任务1执行完毕
执行任务2 IO结束任务2执行完毕
等待IO IO结束任务3执行完毕
执行任务2
等待IO
执行任务3
等待IO
执行任务3
等待IO

可以看到单线程非常消耗时间,同时多线程虽然速度快,但是消耗了3倍的运算资源,并且多线程操作同一个资源的时候存在逻辑设计失误导致的死锁。而事件驱动模式里通过IO等待期间执行其他任务实现CPU资源的高效利用,并且不用担心资源的锁的问题。

事件驱动模型适用于如下场景:
1.程序中有许多任务
2.任务直接高度独立(不需要互相通讯,不需要互相等待)
3.在等待事件到来时,某些任务会阻塞(产生IO空闲,腾出运算空间给其他任务)

'''
#* coding:utf-8 *
'''
事件驱动模型,使用【输入列表】,【输出列表】,【消息队列】 记录事件
通过处理【输入列表】,把处理结果放入【消息队列】,输出放入【输出列表】
然后处理【输出列表】
然后处理【消息队列】
'''
import select
import socket
import sys
import queue
import sys

messages = [
b'message1',
b'sending',
b'connected'
]

server_address = ('localhost',10000)

#创建两个连接
socks = [
socket.socket(socket.AF_INET,socket.SOCK_STREAM),
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
]
socks = [
socket.socket(socket.AF_INET,socket.SOCK_STREAM),
]
print('正在连接%s端口%s'%server_address)

for s in socks:
s.connect(server_address)
for message in messages:
for s in socks:
print('%s :发送"%s" ' % (s.getpeername(),message))
s.send(message)

for s in socks:
    data = s.recv(1024)
    print('%s:接收"%s"'%(s.getsockname(),data))
    if not data:
        print(sys.stderr,'关闭连接',s.getsockname())
                    '''

单线程 多 线 程 事件驱动模型
执行任务1 执行任务1 执行任务2 执行任务3 执行任务1
等待IO 等待IO 等待IO 等待IO 任务1IO空闲时执行任务2
执行任务1 执行任务1 执行任务2 执行任务3 任务2IO空闲时执行任务3
等待IO 等待IO 等待IO 等待IO IO结束任务1执行完毕
执行任务2 IO结束任务2执行完毕
等待IO IO结束任务3执行完毕
执行任务2
等待IO
执行任务3
等待IO
执行任务3
等待IO

可以看到单线程非常消耗时间,同时多线程虽然速度快,但是消耗了3倍的运算资源,并且多线程操作同一个资源的时候存在逻辑设计失误导致的死锁。而事件驱动模式里通过IO等待期间执行其他任务实现CPU资源的高效利用,并且不用担心资源的锁的问题。

事件驱动模型适用于如下场景:
1.程序中有许多任务
2.任务直接高度独立(不需要互相通讯,不需要互相等待)
3.在等待事件到来时,某些任务会阻塞(产生IO空闲,腾出运算空间给其他任务)

'''
#* coding:utf-8 *
'''
事件驱动模型,使用【输入列表】,【输出列表】,【消息队列】 记录事件
通过处理【输入列表】,把处理结果放入【消息队列】,输出放入【输出列表】
然后处理【输出列表】
然后处理【消息队列】
不知道为何调试的时候,调试很慢,大概1分钟后必定会报错。。。直接运行和快速调试可以正常运行
'''
import select
import socket
import sys
import queue

server = socket.socket()
server.setblocking(0)

server_addr = ('localhost',10000)

print('启用%s端口%s'%server_addr)
server.bind(server_addr)
server.listen(5)

inputs = [server,] #把自己添加进输入事件,server本身也是一个fd
outputs = []
message_queues = {} #消息队列
while True:
print('waiting for next event...')
readable,writeable,exeptional = select.select(inputs,outputs,inputs) #如果没有fd就绪,程序会阻塞在这里
for sock in readable: #每个sock就是一个socket ,取出输入队列进行处理
if sock is server : #sock是server类型,代表server就绪了
#新连接来了,接受连接
conn,client_addr = sock.accept()
#print(conn)
#print(sock)
print('接客了',client_addr)
conn.setblocking(0)
inputs.append(conn) #为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据,

把它放到inputs里,下一次loop新连接就会交给server监听,如果这个连接发来数据,

        # 这个连接的fd就会就绪,select把连接返回到readable里,然后我们操作readable列表,取出连接,接收数据
        message_queues[conn] = queue.Queue()#存进队列里
    else:#sock不是server,就是与客户端建立的fd了
        data = sock.recv(1024)
        if data:
            print('接收到了来自[%s]的数据:' % sock.getpeername()[0],data)
            #print(sock)
            message_queues[sock].put(data)     #接收的消息放入队列
            if sock not in outputs:
                outputs.append(sock)          #为了不阻塞其他连接,这里放入输出事件
        else:#如果断开连接
            print('连接断开',sock)
            if sock in outputs:
                outputs.remove(sock)  #清理已经断开的连接
            inputs.remove(sock)       #清理已经断开的连接
            del message_queues[sock]  #清理已经断开的连接
for sock in writeable:   #取出输出队列
    try:
        print("hahahaha",sock)
        print("heheheh",message_queues)
        next_msg = message_queues[sock].get_nowait()
    except queue.Empty:
        print('client [%s]' %sock.getpeername()[0],"队列是空的")
        outputs.remove(sock)
    else:
        print('sending msg to [%s]'%sock.getpeername()[0],next_msg)
        sock.send(next_msg.upper())
for sock in exeptional:  #取出异常队列
    print('exception:',sock.getpeername())
    inputs.remove(sock)
    if sock in outputs:
        outputs.remove(sock)
    sock.close()
    del message_queues[sock]
            '''

selectors模块
'''
#* coding:utf-8 *
'''
selectors 提供高级别的高效多路复用IO,是基于select编译的模块,
官方鼓励用户使用selectors替代select,除非用户想基于控制操作系统级别的使用
'''
import socket
import selectors

sel = selectors.DefaultSelector()
#EVENT_READ = (1 << 0)1 2^0
#EVENT_WRITE = (1 << 1)1
2^1

def accept(sock,mask):
conn , addr = sock.accept()
print('accepted',conn,'from',addr)
conn.setblocking(False)
y= sel.register(conn,selectors.EVENT_READ,read)
#print(y)SelectorKey(fileobj=<socket.socket fd=332, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 10000), raddr=('127.0.0.1', 2034)>, fd=332, events=1, data=<function read at 0x021E25D0>)

def read(conn,mask):
data = conn.recv(1000)
if data:
#eching b'message1' to <socket.socket fd=332, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 10000), raddr=('127.0.0.1', 1940)>
print('echoing',repr(data),'to',conn)
conn.send(data.upper())
else:
print('closing',conn)
sel.unregister(conn) #无连接时关闭
conn.close()

sock = socket.socket()
sock.bind(('localhost',10000))
sock.listen(100)
sock.setblocking(False)

''' def register(self, fileobj, events, data=None): #fileobj是文件描述符,events是掩码,data 是回调的数据
key = super().register(fileobj, events, data) #注册fd
if events & EVENT_READ:
self._readers.add(key.fd)
if events & EVENT_WRITE:
self._writers.add(key.fd)
return key
'''
#SelectorKey(fileobj=<socket.socket fd=268, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 10000)>, fd=268, events=1, data=<function accept at 0x01E63660>)

x= sel.register(sock,selectors.EVENT_READ,accept)
#print(x)
while True:
events = sel.select() #开始阻塞,等待事件 当事件ready,返回这个ready所带的key:【fileobj,evets,data】,events & key.events数据

ready.append((key, events & key.events))

for key ,mask in events:
    callback = key.data#callback是,函数入口。。
    #print(callback)
    callback(key.fileobj,mask)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章