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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章