IO模型和協程

1、 IO模型linux

五種IO模型:程序員

blocking IO :阻塞IOweb

nonblocking IO 非阻塞IO數據庫

IO multiplexing  IO多路複用編程

signal driven IO 信號驅動IOwindows

asynchronous IO 異步IO緩存

對於一個network IO,它會涉及到兩個系統對象,一個是調用這個IO的process(or thread),另外一個就是系統內核。當一個read/recv讀數據的操做發生時,該操做會經歷兩個階段:tomcat

1, 等待數據準備網絡

2, 將數據從內核拷貝到進程中多線程

補充:

1, 輸入操做:read,readv,recv,recvfrom,recvmsg共5個函數,若是會阻塞狀態,則會經歷wait data和copy data兩個階段,若是設置爲非阻塞則在wait不到data時拋出異常

2, 輸出操做:write,writev,send,sendto,sendmsg共5個函數,在發送緩衝區滿了會阻塞在原地,若是設置爲非阻塞,則會拋出異常

3, 接收外來連接:accept,與輸入操做相似

4, 發起外出連接:connect,與操做相似

 

2、阻塞IO

回顧同步/異步/阻塞/非阻塞:

同步:提交一個任務以後要等待這個任務執行完畢

異步:只管提交任務,不等待這個任務執行完畢就能夠去作其餘的事情

阻塞:recv,recvfrom,accept,線程階段 運行狀態>>>阻塞狀態>>>就緒

非阻塞:沒有阻塞狀態

 

在一個線程的IO模型中,咱們recv的地方阻塞,咱們就開啓多線程,可是無論你開啓多少個線程,這個recv的時間是否是沒有被規避掉,不論是多線程仍是多進程都沒有規避掉IO這個時間

 

實際上,除非特別指定,幾乎全部的IO接口(包括socket接口)都是阻塞的。這給網絡編程帶來了一個很大的問題,如在調用recv(1024)的同時,線程將被阻塞,在此期間,線程將沒法執行任何運算或響應任何的網絡請求

一個簡單的解決方案:

在服務端使用多線程(或多進程)。多線程(或多進程)的目的是讓每一個鏈接都擁有獨立的線程(或進程),這樣任何一個鏈接的阻塞都不會影響其餘的鏈接。

該方案的問題是:

開啓多線程或都線程的方式,在遇到要同時響應成百上千的鏈接請求,則不管多線程仍是多進程都會佔用嚴重的系統資源,下降系統對外界相應效率,並且線程與進程自己也更容易進入假死狀態

改進方案:

不少程序員可能會考慮使用「線程池」或「進程池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務。「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。這兩種技術均可以很好的下降系統開銷,都被普遍應用很大型系統,如websphere、tomcat和各類數據庫等

改進後方案其實也存在着問題:

「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用IO接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界的響應並不比沒有池的時候效果好多少。因此使用「池」必須其面臨的響應規模,並根據規模調整「池」的大小

 

3、非阻塞IO

缺點:

1, 循環調用recv()將大幅推高cpu佔用率,這也是咱們在代碼中留一句time.sleep(2)的緣由,不然在低配主機下極容易出現卡機狀況

2, 任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read一次,而任務可能在兩次輪詢之間的任意時間完成。這會致使總體數據吞吐量的下降。

import time
import socket

server = socket.socket()

ip_port = ('127.0.0.1',8001)
server.bind(ip_port)

server.listen()
server.setblocking(False)
conn_list = []

while 1:
    while 1:
        try:
            conn,addr = server.accept()
            conn_list.append(conn)
            break
        except BlockingIOError:
            time.sleep(0.1)
            print('此時尚未人連接我')

    for sock in conn_list:
        print(sock)
        while 1:
            try:
                from_client_msg = sock.recv(1024)
                print(from_client_msg.decode('utf-8'))
                sock.send(b'hello')
                break
            except BlockingIOError:
                print('尚未任何的消息啊')
非阻塞iO模型服務端

 

import socket
client = socket.socket()

client.connect(('127.0.0.1',8001))
while 1:
    to_server_msg = input('我想對你說>>>>')
    client.send(to_server_msg.encode('utf-8'))
    from_server_msg = client.recv(1024)
    print(from_server_msg.decode('utf-8'))
非阻塞IO模型客戶端

4、多路複用IO

結論:select的優點在於能夠處理多個鏈接,不適用於單個鏈接

IO多路複用的機制:

Select:windows 、linux

Poll機制:linux和select監聽機制同樣,可是對監聽列表裏面的數量沒有限制,select默認限制是1024個,可是他們兩個都是操做系統輪詢每個被監聽的文件描述符(若是數量很大,其實效率不太好),卡是否有可讀操做

Epoll:linux它的監聽機制和上面兩個不一樣,他給每個監聽的對象綁定了一個回調函數,你這個對象有消息,那麼觸發回調函數給用戶,用戶就進行系統調用來拷貝數據,並非輪詢監聽全部的被監聽對象,這樣的效率高不少。

 

import select
import socket

server = socket.socket()
server.bind(('127.0.0.1',8001))
rlist = [server,]
server.listen()
while 1:
    print('11111')
    rl,wl,el = select.select(rlist,[],[])#建立rl對象,監聽
    print(222222)
    print('server對象>>>',server)
    print(rl)   #rl對象其實跟server對象(內容)一致
    for sock in rl: #當rl有值的時候,循環列表
        if sock == server: #值與server相同
            conn,addr = sock.accept() #創建鏈接
            rlist.append(conn) #把管道信息加入列表
        else:
            from_client_msg = sock.recv(1024)#conn
            print(from_client_msg.decode('utf-8')) #打印接收
IO多路複用服務端

 

import socket

client = socket.socket()
client.connect(('127.0.0.1',8001))

to_server_msg = input('發給服務端的消息:')
client.send(to_server_msg.encode('utf-8'))
# from_server_msg = client.recv(1024)
# print(from_server_msg.decode('utf-8'))
io多路複用客戶端

 

5、異步IO

 

from gevent import monkey;monkey.patch_all()
import time
import gevent

def func1(n):
    print('xxxxxx',n)
    # gevent.sleep(2)
    time.sleep(2)
    print('cccccc',n)

def func2(m):
    print('111111',m)
    # gevent.sleep(2)
    time.sleep(2)
    print('222222',m)

start_time = time.time()
g1 = gevent.spawn(func1,'alex')
g2 = gevent.spawn(func2,'德瑪西亞')
# g1.join() #
# g2.join()
gevent.joinall([g1,g2])
end_time = time.time()
print(end_time - start_time)

print('代碼結束')
相關文章
相關標籤/搜索