轉自http://blog.csdn.net/gaoxingnengjisuan/article/details/12913275
本來打算總結一下eventlet在OpenStack中的應用,正巧在網上找到幾篇別人已經總結好的資料,而且總結的很好,這裏直接轉載過來。同時也向作者表示感謝。
Eventlet庫在OpenStack服務中上鏡率很高,尤其是在服務的多線程和WSGI Server併發處理請求的情況下,深入瞭解eventlet庫是很必要的。Eventlet庫是由second life開源的高性能網絡庫,從Eventlet的源碼可以知道,其主要依賴於兩個關鍵的庫:
1.greenlet
2.select.epoll (或者epoll等類似的庫)
greenlet庫過程了其併發的基礎,eventlet庫簡單的對其封裝之後,就構成了GreenTread。
select庫中的epoll則是其默認的網絡通信模型。正由於這兩個庫的相對獨立性,可以從兩個方面來學習eventlet庫,首先是greenlet。
greenlet
在介紹GreenThread之前,可以先參考下面三篇文章,對greenlet來個迅速入門。
還補充一篇文檔,寫的很好。
通過這三篇循序漸漸的文章,大概可以瞭解到greenlet是一個稱爲協程(coroutine)的東西,有下面幾個特點。
1.每個協程都有自己的私有stack及局部變量;
2.同一時間內只有一個協程在運行,故無須對某些共享變量加鎖;
3.協程之間的執行順序,完成由程序來控制;
總之,協程就是運行在一個線程內的僞併發方式,最終只有一個協程在運行,然後程序來控制執行的順序。可以看下面的例子來理解上面的意思。
- import greenlet
- def test1(n):
- print "test1:",n
- gr2.switch(32)
- print "test1: over"
- def test2(n):
- print "test2:",n
- gr1.switch(23)
- print "test2: over"
- greenlet = greenlet.greenlet
- current = greenlet.getcurrent()
- gr1 = greenlet(test1,current)
- gr2 = greenlet(test2,current)
- gr1.switch(2)
- test1: 2
- test2: 32
- test1: over
GreenThread
那麼在eventlet中對greenlet進行了簡單的封裝,就成了GreenThread,並且上面的程序還會引來一個問題,如果我們想要寫一個協程,那到底該如何來控制函數的執行過程了,如果協程多了,控制豈不是很複雜了。帶着這個問題來看eventlet的實現。
在介紹下面的內容之前,先貼出eventlet官方的文檔,這個上面詳細的介紹了該如何來使用eventlet庫。我們從其中選出一個接口來分析。spawn函數,調用該函數,將會使用一個GreenThread來執行用戶傳入的函數。函數具體接口如下:
- def spawn(func, *args, **kwargs):
- hub = hubs.get_hub()
- g = GreenThread(hub.greenlet)
- hub.schedule_call_global(0,g.switch,func,args,kwargs)
- return g
hub在eventlet中是一個單太實例,也也就是全局就這有這一個實例,其包含一個greenlet實例,該greenlet實例是self.greenlet = greenlet(self.run),這個實例就是官方文檔說的MAINLOOP,主循環,更加具體就是其中的run方法,是一個主循環。並且該hub還有兩個重要的列表變量,self.timers 和 self.next_timers,前者是一個列表,但是在這個列表上實現了一個最小堆,用來存儲將被調度運行的greenthread,後者,用來存儲新加入的greenthread。
第二,創建一個GreenThread的實例,greenthread繼承於greenlet,簡單封裝了下,該類的構造函數只需要一個參數,父greenlet,然後再自己的構造函數中,調用父類greenlet的構造函數,傳遞兩個參數,GreenTread的main函數和一個greenlet的實例。第二代碼就知道,hubs中作爲MAINLOOP的greenlet是所有先創建的greenthread的父greenlet。由前面介紹greenlet的例子中,我們可以知道,當調用該greenthread的switch方法時,將會開始執行該才傳遞給父類的self.main函數。
第三,然後單態的hub調用schedule_call_global函數,該函數的作用可以看其註釋,用來調度函數去執行。
- """
- Schedule a callable to be called after 'seconds' seconds have
- elapsed. The timer will NOT be canceled if the current greenlet has
- exited before the timer fires.
- seconds: The number of seconds to wait.
- cb: The callable to call after the given time.
- *args: Arguments to pass to the callable when called.
- **kw: Keyword arguments to pass to the callable when called.
- """
- t = timer.Timer(seconds, cb, *args, **kw)
- self.add_timer(t)
- return t
這三步結束之後,對spawn的調用就返回了,然而現在只是創建了一個GreenThread,還沒有調度它去執行,最後還需要再返回的結果上調用g.wait()方法,這樣就開始GreenThread的神奇之旅了。
我們看GreenThread的wait方法的具體代碼:
- def __init__(self, parent):
- greenlet.greenlet.__init__(self, self.main, parent)
- self._exit_event = event.Event()
- self._resolving_links = False
- def wait(self):
- """ Returns the result of the main function of this GreenThread. If the
- result is a normal return value, :meth:`wait` returns it. If it raised
- an exception, :meth:`wait` will raise the same exception (though the
- stack trace will unavoidably contain some frames from within the
- greenthread module)."""
- return self._exit_event.wait()
在該while循環中,就對self.next_timers中的timers做處理:
- def prepare_timers(self):
- heappush = heapq.heappush
- t = self.timers
- for item in self.next_timers:
- if item[1].called:
- self.timers_canceled -= 1
- else:
- heappush(t, item)
- del self.next_timers[:]
加入到heap中的timers這會按照順序開始依次遍歷,如果到了他們的執行時間點了,timer對象就會直接被調用。看下面的代碼:
- def fire_timers(self, when):
- t = self.timers
- heappop = heapq.heappop
- while t:
- next = t[0]
- exp = next[0]
- timer = next[1]
- if when < exp:
- break
- heappop(t)
- try:
- if timer.called:
- self.timers_canceled -= 1
- else:
- timer()
- except self.SYSTEM_EXCEPTIONS:
- raise
- except:
- self.squelch_timer_exception(timer, sys.exc_info())
- clear_sys_exc_info()
Timer對象重載了__call__方法,所以可以直接調用了,timer被調用之後,我們前面知道,傳遞進來的是g.switch,在timer中就是調用了該switch函數,直接觸動了greenthread的執行,此時,我們自定義的函數就可以被執行了。
我們知道,如果我們自定義的函數要運行時間很長,怎麼辦,其他的greenthread則沒有機會去運行了,在openstack nova官方文檔中介紹thread中也提到這個問題,此時我們需要在自己定義的函數中調用greenthread.sleep(0)函數,來進行切換,使其他的greenthread也能被調度運行。看看greenthread.sleep函數的代碼。
- def sleep(seconds=0):
- """Yield control to another eligible coroutine until at least *seconds* have
- elapsed.
- *seconds* may be specified as an integer, or a float if fractional seconds
- are desired. Calling :func:`~greenthread.sleep` with *seconds* of 0 is the
- canonical way of expressing a cooperative yield. For example, if one is
- looping over a large list performing an expensive calculation without
- calling any socket methods, it's a good idea to call ``sleep(0)``
- occasionally; otherwise nothing else will run.
- """
- hub = hubs.get_hub()
- current = getcurrent() # 當前正在執行的greenthread,調用這個sleep函數
- assert hub.greenlet is not current, 'do not call blocking functions from the mainloop'
- timer = hub.schedule_call_global(seconds, current.switch)
- try:
- hub.switch()
- finally:
- timer.cancel()
至此,調度的過程就大致描述結束了。
greenthread中其他的函數都基本同樣,如果我們的函數只是簡單的進行CPU運行,而不涉及到IO處理,上面的知識就可以理解eventlet了,然而,eventlet是一個高性能的網絡庫,還有很大一部分是很網絡相關的。在留給下次。