python之I/O操作


IO在計算機中指Input/Output,也就是輸入和輸出。由於程序和運行時數據是在內存中駐留,由CPU這個超快的計算核心來執行,涉及到數據交換的地方,通常是磁盤、網絡等,就需要IO接口。

比如你打開瀏覽器,訪問新浪首頁,瀏覽器這個程序就需要通過網絡IO獲取新浪的網頁。瀏覽器首先會發送數據給新浪服務器,告訴它我想要首頁的HTML,這個動作是往外發數據,叫Output,隨后新浪服務器把網頁發過來,這個動作是從外面接收數據,叫Input。所以,通常,程序完成IO操作會有Input和Output兩個數據流。當然也有只用一個的情況,比如,從磁盤讀取文件到內存,就只有Input操作,反過來,把數據寫到磁盤文件里,就只是一個Output操作。

事件驅動模型

通常情況,有一下幾種情況模型:

  1. 每收到一個請求,創建一個新的進程,來處理該請求。
  2. 每收到一個請求,創建一個新的線程,來處理該請求。
  3. 每收到一個請求,放入一個時間列表,讓主進程通過非阻塞IO來處理請求。

綜上普遍認為第三種方式為大多數網絡服務器采用的方式。

例如在UI編程中,常常用到鼠標點擊進行操作,那么如何,何時去獲得鼠標的點擊進行處理呢?

在前面學到的線程中,我們可以創建一個線程對鼠標進行檢測。那么問題來了?

  1. CPU資源浪費,可能鼠標點擊的頻率非常小,但是掃描線程還是會一直循環檢測,這會造成很多的CPU資源浪費;如果掃描鼠標點擊的接口是阻塞的呢?
  2. 如果是堵塞的,又會出現下面這樣的問題,如果我們不但要掃描鼠標點擊,還要掃描鍵盤是否按下,由於掃描鼠標時被堵塞了,那么可能永遠不會去掃描鍵盤;
  3. 如果一個循環需要掃描的設備非常多,這又會引來響應時間的問題;

所以此方式是不可取的

第二種就是剛提到的事件驅動模型

目前大部分的UI編程都是事件驅動模型。如很多UI平台都會提供onClick()事件,這個事件就代表鼠標按下事件。事件驅動模型大體思路如下:

  1. 有一個消息隊列
  2. 鼠標點擊時,往這個隊列里增加一個點擊事件。
  3. 有個循環,不斷的從隊列中取出事件,根據不同事件調用不同的函數。
  4. 事件一般都各自保存各自的處理函數指針,這樣,每個消息都有獨立的處理函數。

簡單的代碼示例如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p onclick="func()">點我</p>

<script>
    function func() {
        alert("你好")
    }

</script>
</body>
</html>

圖形講解

事件驅動編程是一種編程范式,這里程序的執行流由外部事件來決定。它的特點是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程范式是(單線程)同步以及多線程編程。

用個簡單的圖例來講解三者關系

圖中灰色部分表示I/O操作阻塞的時間

同步IO和異步IO

於CPU和內存的速度遠遠高於外設的速度,所以,在IO編程中,就存在速度嚴重不匹配的問題。舉個例子來說,比如要把100M的數據寫入磁盤,CPU輸出100M的數據只需要0.01秒,可是磁盤要接收這100M數據可能需要10秒,怎么辦呢?有兩種辦法:

  1. 第一種是CPU等着,也就是程序暫停執行后續代碼,等100M的數據在10秒后寫入磁盤,再接着往下執行,這種模式稱為同步IO;
  2. 另一種方法是CPU不等待,只是告訴磁盤,“您老慢慢寫,不着急,我接着干別的事去了”,於是,后續代碼可以立刻接着執行,這種模式稱為異步IO。

同步和異步的區別就在於是否等待IO執行的結果。舉個例子來說。

好比你去麥當勞點餐,你說“來個漢堡”,服務員告訴你,對不起,漢堡要現做,需要等5分鍾,於是你站在收銀台前面等了5分鍾,拿到漢堡再去逛商場,這是同步IO。

你說“來個漢堡”,服務員告訴你,漢堡需要等5分鍾,你可以先去逛商場,等做好了,我們再通知你,這樣你可以立刻去干別的事情(逛商場),這是異步IO。

異步IO模型

異步IO全程過程沒有阻塞狀態。

注: 異步IO模塊3.0才有,叫asyncio

阻塞IO(blocking IO )

就上面的例子你去麥當勞點漢堡,但此時沒有了,要5分鍾后才出來,而你咬着等5分鍾,此刻的5分鍾就浪費了,這就是典型的阻塞IO。大概流程可以這樣表示

非阻塞IO(non-blocking IO)

假設你不想在那等,就出去辦其他的事了,但是你又擔心你點的漢堡被別人拿走了,你就來來回回好多趟,最后終於做好了。大概流程如下

需要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態

 IO多路復用(IO multiplexing)

它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。就如我們點好餐后,不用每次都去問服務員了,看專門的顯示屏即可知道,那樣每個顧客都知道自己的等待時間了。它的流程如圖:

注意:當用戶進程調用了select時,那么整個進程就會阻塞住。看似和阻塞IO沒有太大的區別,但是這可以同時監聽處理多個connection。整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

綜上所述,對幾種IO模型進行比較如下:

IO多路復用select,poll,epoll 

 select

select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用后select函數會阻塞,直到有描述符就緒(可讀、可寫、或except),或超時(timeout等待時間,如立即返回設為null即可),函數返回。當select函數返回后,可以通過遍歷fdset,來找到就緒的描述符。

select優勢在於幾乎支持所有平台。

缺點在於

  1. 單個進程能夠監視的文件描述符的數量存在最大限制,默認為1024.
  2. 每次select()都要輪詢遍歷FD_SETSIZE個Socket來完成調度,效率較低。
  3. 需要維護一個用來存放大量fd的數據結構,使得用戶空間和內核空間在傳遞時復制開銷大。

poll

相當於是select和epoll之間的過度,唯一的改動就是沒有了文件描述符的數量限制

epoll(linux特有的實現方法,目前windows不支持)

epoll有兩種模式,區別在當epoll_wait檢測到描述符事件發生並將此事件通知應用程序。

  • LT模式:應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。
  • ET模式:應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。須使用非阻塞套接口

最后舉個並發聊天的例子如

import socket
import select

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(3)

sk_list = [sk, ]

while True:
    r_list, w_list, x_list = select.select(sk_list, [], [],)

    for i in r_list:
        if i == sk:
            conn, addr = i.accept()   #接收第一個傳進來的也就是sk,之后開始執行conn了
            sk_list.append(conn)
        elif i == conn:

            data = conn.recv(1024)
            data = data.decode('utf8')
            try:
                # data is False
                if data is not False:
                    print(data)
                    inp = input("回復客戶端 %s >>>>" % sk_list.index(i))
                    conn.sendall(inp.encode('utf8'))


            except Exception:
                # print(e)
                sk_list.remove(i)
server
import socket

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sk.connect(('127.0.0.1', 8080))

while True:

    inp = input(">>>>")
    sk.sendall(inp.encode('utf8'))
    data = sk.recv(1024)
    print(data.decode('utf8'))
client

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM