python eventlet並發原理分析

來源:互聯網
上載者:User
最近在學習eventlet這個強悍的東東,看到我同事的一些整理。故貼出來,大家一起分享~
motivation

114.113.199.11伺服器上nova服務中基於python eventlet實現的定時任務(periodic_task)和 心跳任務(report_state)都是eventlet的一個greenthread執行個體.

目前伺服器上出現了nova定時任務中某些任務執行時間過長而導致心跳任務不能準時啟動並執行問題.

如果eventlet是一個完全意義上的類似線程/進程的並發庫的話, 不應該出現這個問題, 需要研究 eventlet的並發實現, 瞭解它的並發實現原理, 避免以後出現類似的問題. 分析

經過閱讀eventlet原始碼, 可以知道eventlet主要依賴另外2個python package: greenlet python-epoll (或其他類似的非同步IO庫, 如poll/select等)

主要做了3個工作: 封裝greenlet 封裝epoll 改寫python標準庫中相關的module, 以便支援epoll epoll

epoll是linux實現的一個基於事件的非同步IO庫, 在之前類似的非同步IO庫poll上改進而來.

下面兩個例子會示範如何用epoll將阻塞的IO操作用epoll改寫為非同步非阻塞. (取自官方文檔) blocking IO

    import socket    EOL1 = b'\n\n'    EOL2 = b'\n\r\n'    response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'    response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'    response += b'Hello, world!'    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    serversocket.bind(('0.0.0.0', 8080))    serversocket.listen(1)    try:        while True:            connectiontoclient, address = serversocket.accept()            request = b''            while EOL1 not in request and EOL2 not in request:                request += connectiontoclient.recv(1024)            print('-'*40 + '\n' + request.decode()[:-2])            connectiontoclient.send(response)            connectiontoclient.close()    finally:        serversocket.close()

這個例子實現了一個簡單的監聽在8080連接埠的web伺服器. 通過一個死迴圈不停的接收來自8080連接埠 的串連, 並返回結果.

需要注意的是程式會在

connectiontoclient, address = serversocket.accept()

這一行block住, 直到擷取到新的串連, 程式才會繼續往下運行.

同時, 這個程式同一個時間內只能處理一個串連, 如果有很多使用者同時訪問8080連接埠, 必須要按先後 順序依次處理這些串連, 前面一個串連成功返回後, 才會處理後面的串連.

下面的例子將用epoll將這個簡單的web伺服器改寫為非同步方式 non-blocking IO by using epoll

import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)serversocket.bind(('0.0.0.0', 8080))serversocket.listen(1)serversocket.setblocking(0)epoll = select.epoll()epoll.register(serversocket.fileno(), select.EPOLLIN)try:    connections = {}; requests = {}; responses = {}    while True:        events = epoll.poll(1)        for fileno, event in events:            if fileno == serversocket.fileno():                connection, address = serversocket.accept()                connection.setblocking(0)                epoll.register(connection.fileno(), select.EPOLLIN)                connections[connection.fileno()] = connection                requests[connection.fileno()] = b''                responses[connection.fileno()] = response            elif event & select.EPOLLIN:                requests[fileno] += connections[fileno].recv(1024)                if EOL1 in requests[fileno] or EOL2 in requests[fileno]:                    epoll.modify(fileno, select.EPOLLOUT)                    print('-'*40 + '\n' + requests[fileno].decode()[:-2])            elif event & select.EPOLLOUT:                byteswritten = connections[fileno].send(responses[fileno])                responses[fileno] = responses[fileno][byteswritten:]                if len(responses[fileno]) == 0:                    epoll.modify(fileno, 0)                    connections[fileno].shutdown(socket.SHUT_RDWR)            elif event & select.EPOLLHUP:                epoll.unregister(fileno)                connections[fileno].close()                del connections[fileno]finally:    epoll.unregister(serversocket.fileno())    epoll.close()    serversocket.close()

可以看到, 例子中首先使用serversocket.setblocking(0)將socket設為非同步模式, 然後 用select.epoll()建立了一個epoll, 接著用epoll.register(serversocket.fileno(), select.EPOLLIN) 將該socket上的IO輸入事件(select.EPOLLIN)註冊到epoll裡. 這樣做了以後, 就可以將 上面例子中會在socket.accept()這步阻塞的Main Loop改寫為基於非同步IO事件的epoll迴圈了.

events = epoll.poll(1)

簡單的說, 如果有很多使用者同時串連到8080連接埠, 這個程式會同時accept()所有的socket串連, 然後通過這行代碼將發生IO事件socket放到events中, 並在後面迴圈中處理. 沒有發生IO事件的 socket不會在loop中做處理. 這樣使用epoll就實現了一個簡單的並發web伺服器.

注意, 這裡提到的並發, 和我們通常所理解線程/進程的並發並不太一樣, 更準確的說, 是 IO多工 . greenlet

greentlet是python中實現我們所謂的"Coroutine(協程)"的一個基礎庫.

看了下面的例子就明白了.

    from greenlet import greenlet    def test1():        print 12        gr2.switch()        print 34    def test2():        print 56        gr1.switch()        print 78    gr1 = greenlet(test1)    gr2 = greenlet(test2)    gr1.switch()

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.