[gevent源碼分析] libev cython綁定core.pyx

gevent core就是封裝了libev,使用了cython的語法,感興趣童鞋可以好好研究研究。其實libev是有python的封裝

pyev(https://pythonhosted.org/pyev/),不過pyev是使用C來寫擴展的,代碼巨複雜。在看core.pyx代碼之前先學習一下

core.pyx用到的cython知識。


一: cython基礎知識

1.cdef, def, cpdef的區別
cdef用於定義C中的函數,變量,如cdef int i;而def知識python中的函數定義方法,一般只是爲了提供python的訪問。
cdef定義的函數,變量在python環境中是訪問不了的,要麼提供一個def的包裝方法,要麼用cpdef。cpdef只用於定義函數,
速度比cdef稍慢,主要因爲cpdef定義的類函數支持重載,調用的時候需要查找虛函數表,cpdef同時生成供cython和python
調用的函數

2.明確的類型聲明
爲了提高速度和可讀性,cython建議所有的變量加上類型,包括python中的類型。如定義列表,cdef list result;
如果是類可用cdef object p;

3.class如何定義,public的用法
我們看下core.pyx提供的class loop的定義
cdef public class loop [object PyGeventLoopObject, type PyGeventLoop_Type]:
    cdef libev.ev_loop* _ptr
    cdef public object error_handler
    cdef libev.ev_prepare _prepare
    cdef public list _callbacks
    cdef libev.ev_timer _timer0
你可能好奇,loop後面的中括號是幹嘛用的?我們想想python中int類型的PyIntObject,
typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;
同樣我們會生PyGeventLoopObject對象
struct PyGeventLoopObject {
  PyObject_HEAD
  struct __pyx_vtabstruct_6gevent_4core_loop *__pyx_vtab;
  struct ev_loop *_ptr;
  PyObject *error_handler;
  struct ev_prepare _prepare;
  PyObject *_callbacks;
  struct ev_timer _timer0;
}
而PyGeventLoop_Type就是我們用來定義一個類的類型對象
DL_EXPORT(PyTypeObject) PyGeventLoop_Type = {
  PyVarObject_HEAD_INIT(0, 0)
  __Pyx_NAMESTR("gevent.core.loop"), /*tp_name*/
  sizeof(struct PyGeventLoopObject), /*tp_basicsize*/
  0, /*tp_itemsize*/
  __pyx_tp_dealloc_6gevent_4core_loop, /*tp_dealloc*/
  0, /*tp_print*/
  0, /*tp_getattr*/
  0, /*tp_setattr*/
...
}
我們再來看看public的問題,這裏的public也就是說這個class將會提供給其它.c文件使用,所以會生成對應的.h文件。
而cdef public list _callbacks,這個public和函數說明cpdef有點類似,也就是提供了在python中訪問_callbacks的方法。

二: 源碼一覽

def __init__(self, object flags=None, object default=None, size_t ptr=0):
        pass
flags: 確定後端使用的異步IO模型,如"select, epoll",可直接字符串也可數字(需參考libev/ev.h)
default:是否使用libev的默認loop,否則將創建一個新的loop
可通過loop.backend確定是否和你設置一致,loop.backend_int返回libev內部對應序號
如:
from gevent import core
flag = "select"
loop=core.loop(flag)
assert loop.backend == flag
assert core._flags_to_int(flag) == loop.backend_int

libev支持的watcher:
所有watcher都通過start啓動,並傳遞迴調函數
1.io:
    loop.io(int fd, int events, ref=True, priority=None)
        fd: 文件描述符,可通過sock.fileno()獲取
        events: 事件 1:read 2:write 3.read_write

        下面兩個參數所有watcher都適用
        ref: 是否增加mainLoop的引用次數,默認是增加的。在libev中watcher.start都會增加引用次數,watcher.stop都會減少引用次數。當libev發現引用次數爲0,也就沒有需要監視的watcher,循環就會退出。
        priority: 設置優先級

2.timer定時器
    loop.timer(double after, double repeat=0.0, ref=True, priority=None)
        after: 多久後啓動
        repeat: 多次重複之間間隔
    可通過一下小程序看看:

    def f():
        print time.time()
        print 'eeeee'
    from gevent.core import loop
    l = loop()
    timer = l.timer(2,3) #2秒後啓動,3秒後再次啓動
    print time.time()
    timer.start(f)
    l.run()

3.signer信號 收到信號處理方式
    loop.signal(int signum, ref=True, priority=None)
hub中有封裝signal,使用如下:
def f():
    raise ValueError('signal')
sig = gevent.signal(signal.SIGALRM, f)
assert sig.ref is False
signal.alarm(1)
try:
    gevent.sleep(2)
    raise AssertionError('must not run here')
except ValueError:
    assert str(sys.exc_info()[1]) == 'signal'
和其它watcher不同的是ref默認是False,因爲信號並不是必須的,所以循環不需等待信號發生。

4.async 喚醒線程
    loop.async(ref=True, priority=None)
    這主要是通過管道實現的,async.send方法將向管道發送數據,循環檢查到讀事件喚醒線程.
    hub = gevent.get_hub()
    watcher = hub.loop.async()
    gevent.spawn_later(0.1, thread.start_new_thread, watcher.send, ())
    start = time.time()
    with gevent.Timeout(0.3):
        hub.wait(watcher)
gevent中線程池中使用了async,當worker線程運行回調函數後,設置返回值,通過async.send喚醒hub主線程

5.fork 子進程事件
    loop.fork(ref=True, priority=None)
    當調用fork時將會回調註冊的子進程watcher,但必須得調用libev.ev_loop_fork纔有效,
而且要在子進程中使用libev也必須要調用libev.ev_loop_fork
    
在gevent的threadpool中使用了fork監視器
    self.fork_watcher = hub.loop.fork(ref=False)
    self.fork_watcher.start(self._on_fork)
    def _on_fork(self):
        # fork() only leaves one thread; also screws up locks;
        # let's re-create locks and threads
        pid = os.getpid()
        if pid != self.pid:
            self.pid = pid
            # Do not mix fork() and threads; since fork() only copies one thread
            # all objects referenced by other threads has refcount that will never
            # go down to 0.
            self._init(self._maxsize)
    回調_on_fork目的就是重新初始化線程池,但是剛纔說了子進程要有效必須要調用libev.ev_loop_fork,
這又是在在哪裏調用的呢?
if hasattr(os, 'fork'):
    _fork = os.fork

    def fork():
        result = _fork()
        if not result: #子進程
            reinit() #調用libev.ev_loop_fork
        return result
問題的關鍵就是gevent/os.py中重定義了fork函數,當fork返回0,也就是子進程,將調用reinit
最後真正調用的就是core.pyx的loop.reinit
    def reinit(self):
        if self._ptr:
            libev.ev_loop_fork(self._ptr)
gevent中的線程池在gevent中使用的很廣,尤其是windows中,如dns請求,os.read write都是通過線程池,
花點時間看看threadpool.py源碼,會收穫很多。

6.ev_prepare  每次event loop之前事件
    loop.prepare(ref=True, priority=None)
    還記得上面timeout中說的,在loop中回調比定時器優先級高,在loop中是沒有添加回調的,gevent是通過
    ev_prepare實現的。

gevent loop.run_callback實現原理:
    1.loop.run_callback會向loop._callbacks中添加回調
    2.在loop的__init__中初始化prepare: libev.ev_prepare_init(&self._prepare, <void*>gevent_run_callbacks)
        註冊回調爲gevent_run_callbacks
    3.在gevent_run_callbacks中會調用loop的_run_callbacks
        result = ((struct __pyx_vtabstruct_6gevent_4core_loop *)loop->__pyx_vtab)->_run_callbacks(loop);
    4.loop的_run_callbacks中會逐個調用_callbacks中的回調
這也就是爲什麼說callback優先級高的原因。

loop.run_callback返回的是一個callback對象,具有stop(),pending屬性,也就是說如果回調還沒運行,我們可以通過stop()方法停止。
事例代碼如下:
def f(a):
    a.append(1)

from gevent.hub import get_hub
loop = get_hub().loop
a= []
f = loop.run_callback(f,a)
f.stop()
gevent.sleep(0)
assert not f.pending #沒有阻塞可能是已運行或被停止
assert not a
如果註釋掉f.stop(),那麼a是[1],因爲gevent.sleep(0)也是直接run_callback,肯定是誰先加入誰先調用,
但如果是其它watcher就沒有機會調用了

7.ev_check 每次event loop之後事件
    loop.check(ref=True, priority=None)
    這個和ev_prepare剛好相反

8.stat 文件屬性變化
    loop.stat(path, float interval=0.0, ref=True, priority=None)
    interval說明期望多久以後libev開始檢測文件狀態變化

開兩個窗口,一個運行該程序,另一個可touch cs.log文件,文件有無也是狀態變化
開兩個窗口,一個運行該程序,另一個可touch cs.log文件,文件有無也是狀態變化
hub = gevent.get_hub()
filename = 'cs.log'
watcher = hub.loop.stat(filename,2) #2s以後才監聽文件狀態
def f():
    print os.path.exists(filename)
watcher.start(f)
gevent.sleep(100)

core.pyx中封裝的watcher差不多都介紹完了,我們看一下libev的主循環ev_run
int
ev_run (EV_P_ int flags)
{
  do
    {
      ......
    }
  while (expect_true (
    activecnt
    && !loop_done
    && !(flags & (EVRUN_ONCE | EVRUN_NOWAIT))
  ));

  return activecnt;
}
其中activecnt就是我們上面說的loop的引用計數,所以除非特殊情況ref最好爲True。

core.pyx細節問題還需要小夥伴們自己去研究,基本的使用上面已經說得很明白了。gevent還剩下什麼呢?也許threadpool,dns也會具體分析一下,敬請期待。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章