歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Python IO多路復用

Python IO多路復用

日期:2017/3/1 9:13:35   编辑:Linux編程

IO多路復用是一個系統層面的概念,讓我們先搞清楚為什麼使用IO多路復用:  

由於進程的執行過程是線性的(也就是順序執行),當我們調用低速系統I/O(read,write,accept等等),進程可能阻塞,此時進程就阻塞在這個調用上,不能執行其他操作.阻塞很正常. 接下來考慮這麼一個問題:一個服務器進程和一個客戶端進程通信,服務器端read(sockfd1,bud,bufsize),此時客戶端進程沒有發送數據,那麼read(阻塞調用)將阻塞直到客戶端調用write(sockfd,but,size)發來數據. 在一個客戶和服務器通信時這沒什麼問題,當多個客戶與服務器通信時,若服務器阻塞於其中一個客戶sockfd1,當另一個客戶的數據到達套接sockfd2時,服務器不能處理,仍然阻塞在read(sockfd1,...)上;此時問題就出現了,不能及時處理另一個客戶的服務,咋麼辦?I/O多路復用來解決! IO多路復用本質上是在同一個線程或進程中,通過撥動開關的方式來執行多個IO操作。注意實際上每個IO操作都是獨立進行的。只是由原來的一對一變成了多對多。如下圖:
select, poll, epoll 都是I/O多路復用的具體的實現,之所以有這三個鬼存在,其實是他們出現是有先後順序的。

I/O多路復用這個概念被提出來以後, select是第一個實現 (1983 左右在BSD裡面實現的)。

select 被實現以後,很快就暴露出了很多問題。
  • select 會修改傳入的參數數組,這個對於一個需要調用很多次的函數,是非常不友好的。
  • select 如果任何一個sock(I/O stream)出現了數據,select 僅僅會返回,但是並不會告訴你是那個sock上有數據,於是你只能自己一個一個的找,10幾個sock可能還好,要是幾萬的sock每次都找一遍,這個無謂的開銷就頗有海天盛筵的豪氣了。
  • select 只能監視1024個鏈接, 這個跟草榴沒啥關系哦,linux 定義在頭文件中的,參見FD_SETSIZE。
  • select 不是線程安全的,如果你把一個sock加入到select, 然後突然另外一個線程發現,尼瑪,這個sock不用,要收回。對不起,這個select 不支持的,如果你喪心病狂的竟然關掉這個sock, select的標准行為是。。呃。。不可預測的, 這個可是寫在文檔中的哦.
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
霸不霸氣

於是14年以後(1997年)一幫人又實現了poll, poll 修復了select的很多問題,比如
  • poll 去掉了1024個鏈接的限制,於是要多少鏈接呢, 主人你開心就好。
  • poll 從設計上來說,不再修改傳入數組,不過這個要看你的平台了,所以行走江湖,還是小心為妙。
其實拖14年那麼久也不是效率問題, 而是那個時代的硬件實在太弱,一台服務器處理1千多個鏈接簡直就是神一樣的存在了,select很長段時間已經滿足需求。

但是poll仍然不是線程安全的, 這就意味著,不管服務器有多強悍,你也只能在一個線程裡面處理一組I/O流。你當然可以那多進程來配合了,不過然後你就有了多進程的各種問題。

於是5年以後, 在2002, 大神 Davide Libenzi 實現了epoll.

epoll 可以說是I/O 多路復用最新的一個實現,epoll 修復了poll 和select絕大部分問題, 比如:
  • epoll 現在是線程安全的。
  • epoll 現在不僅告訴你sock組裡面數據,還會告訴你具體哪個sock有數據,你不用自己去找了。

比較坑爹的是epoll只用Linux提供支持,默認集成到了Linux內核中。

1 2 3 4 5 6 Windows Python: 提供: select Mac Python: 提供: select Linux Python: 提供: select、poll、epoll

  對於select操作:

1 2 3 4 5 6 7 8 9 10 11 句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超時時間) 參數: 可接受四個參數(前三個必須) 返回值:三個列表 select方法用來監視文件句柄,如果句柄發生變化,則獲取該句柄。 1、當 參數1 序列中的句柄發生可讀時(accetp和read),則獲取發生變化的句柄並添加到 返回值1 序列中 2、當 參數2 序列中含有句柄時,則將該序列中所有的句柄添加到 返回值2 序列中 3、當 參數3 序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到 返回值3 序列中 4、當 超時時間 未設置,則select會一直阻塞,直到監聽的句柄發生變化 當 超時時間 = 1時,那麼如果監聽的句柄均無任何變化,則select會阻塞 1 秒,之後返回三個空列表,如果監聽的句柄有變化,則直接執行。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import select

sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind(('127.0.0.1',8002))
sk1.listen(5)
sk1.setblocking(0)

inputs = [sk1,]

while True:
    readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
    for r in readable_list:
        # 當客戶端第一次連接服務端時
        if sk1 == r:
            print 'accept'
            request, address = r.accept()
            request.setblocking(0)
            inputs.append(request)
        # 當客戶端連接上服務端之後,再次發送數據時
        else:
            received = r.recv(1024)
            # 當正常接收客戶端發送的數據時
            if received:
                print 'received data:', received
            # 當客戶端關閉程序時
            else:
                inputs.remove(r)

sk1.close()
利用select實現偽並發 服務端
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

ip_port = ('127.0.0.1',8002)
sk = socket.socket()
sk.connect(ip_port)

while True:
    inp = raw_input('please input:')
    sk.sendall(inp)
sk.close()

利用select實現偽同時處理多個Socket客戶端請求:客戶端

Copyright © Linux教程網 All Rights Reserved