gevent core就是封裝了libev,使用了cython的語法,感興趣童鞋可以好好研究研究。其實libev是有python的封裝
pyev(https://pythonhosted.org/pyev/),不過pyev是使用C來寫擴展的,代碼巨複雜。在看core.pyx代碼之前先學習一下
core.pyx用到的cython知識。
一: cython基礎知識
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;
}
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
所有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()
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,因爲信號並不是必須的,所以循環不需等待信號發生。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主線程loop.fork(ref=True, priority=None)
當調用fork時將會回調註冊的子進程watcher,但必須得調用libev.ev_loop_fork纔有效,
而且要在子進程中使用libev也必須要調用libev.ev_loop_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源碼,會收穫很多。
loop.prepare(ref=True, priority=None)
還記得上面timeout中說的,在loop中回調比定時器優先級高,在loop中是沒有添加回調的,gevent是通過
ev_prepare實現的。
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)
開兩個窗口,一個運行該程序,另一個可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。