前段時間使用libevent網路庫實現了乙個遊戲伺服器引擎,在此記錄下其中遇到的乙個問題。
我在設計伺服器上選擇把邏輯和網路分執行緒,執行緒之間通訊使用佇列。但是這樣做會有個問題:
當邏輯執行緒想要主動的發乙個資料報的時候,網路執行緒此時可能還阻塞在等待網路io的系統呼叫上(比如說epoll)。如果不做特殊處理的話,此時訊息包就會一直積壓在緩衝區中,直到下一次網路執行緒從掛起的系統呼叫返回(比如來了乙個訊息包)。因此,當邏輯執行緒傳送訊息包的時候(bufferevent
_write)需要一種喚醒機制,讓網路執行緒從epoll等系統呼叫中返回並處理傳送訊息包邏輯。
由於對libevent的api不熟悉,起初我是自己實現這個功能的。實現確實是不複雜,但是缺違背了我的初心:只寫簡單必要的**,保證盡可能少的bug。直到後來和同事**(爭論:p)了一番,才發現原來libevent是有對此做支援的,但是具體怎麼做,文件裡面沒有詳細的說,因此同事也說不出個所以然。鑑於此情況,我決定,把libevent中與此相關的原始碼粗略的過一遍,以求能弄明白以下兩件事:
(1)與跨執行緒喚醒事件等待相關的api有哪些,以及如何使用?
(2)這些api背後到底做了哪些工作?
相關api,及用法
/*call event_use_pthreads() if use posix threads.*/evthread_use_windows_threads();
struct event_base* ev_base = event_base_new();
需要注意的是函式evthread_use_windows_threads的呼叫必須在初始化event_base之前。在此之後,無需再做別的事情,邏輯執行緒在執行bufferevent_write的時候,libevent就會自動喚醒網路執行緒的事件迴圈,並執行傳送資料。
隱藏在api背後的邏輯
先看看evthread_use_windows_threads函式做了什麼?
int evthread_use_windows_threads(void)
通過呼叫evthread_use_windows_threads,我們設定了一些**函式,包括設定了libevent獲取執行緒id的**函式evthread_id_fn_。
看看初始化事件迴圈的函式event_base_new做了什麼:
// event.cstruct event_base *
event_base_new(void)
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
}#endif
...}int
evthread_make_base_notifiable(struct event_base *base)
static int
evthread_make_base_notifiable_nolock_(struct event_base *base) else
base->th_notify_fn = notify;
}
它通過如下呼叫:
event_base_new-->event_base_new_with_config-->evthread_make_base_notifiable-->evthread_make_base_notifiable_nolock_
最後通過evutil_make_internal_pipe_函式建立了兩個互相連線的socket(windows環境下,用此來模擬pipe):
/* internal function: set fd[0] and fd[1] to a pair of fds such that writes on* fd[1] get read from fd[0]. make both fds nonblocking and close-on-exec.
* return 0 on success, -1 on failure.
*/int
evutil_make_internal_pipe_(evutil_socket_t fd[2])
return 0;
} else
#endif
#ifdef _win32
#define local_socketpair_af af_inet
#else
#define local_socketpair_af af_unix
#endif
if (evutil_socketpair(local_socketpair_af, sock_stream, 0, fd) == 0)
return 0;
} fd[0] = fd[1] = -1;
return -1;
}
之後,再設定用於喚醒操作的notify函式(evthread_notify_base_default):
static intevthread_notify_base_default(struct event_base *base)
可以看出,在windows下libevent的喚醒機制實際也是self pipe trick,只不過它通過構造一對socket來模擬pipe,當需要喚醒的時候,它就往其中乙個socket寫入1個位元組。
再去看看bufferevent_write:
// bufferevent.cintbufferevent_write(struct bufferevent *bufev, const void *data, size_t size)
// buffer.c
intevbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen)
它會觸發一系列列**函式,而這些**函式在建立bufferevent的時候被指定:
//bufferevent_sock.cstruct bufferevent *
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options)
static void
bufferevent_socket_outbuf_cb(struct evbuffer *buf, const struct evbuffer_cb_info *cbinfo, void *arg) }}
intbufferevent_add_event_(struct event *ev, const struct timeval *tv)
intevent_add(struct event *ev, const struct timeval *tv)
intevent_add_nolock_(struct event *ev, const struct timeval *tv, int tv_is_absolute)
由**可知,在往bufferevent寫資料後執行的**函式中,就有喚醒網路執行緒邏輯(evthread_notify_base)。那為什麼還需要手動呼叫evthread_use_windows_threads函式呢?
這裡再說一下:
#define evbase_need_notify(base) \((base)->running_loop && \
((base)->th_owner_id != evthreadimpl_get_id_()))
unsigned long
evthreadimpl_get_id_()
之前說過,當呼叫evthread_use_windows_threads,設定了libevent獲取執行緒id的**函式evthread_id_fn_。也正因為此,才會去跑下去執行evthread_notify_base函式:
static intevthread_notify_base(struct event_base *base)
所以,當我們在邏輯執行緒呼叫bufferevent_write嘗試傳送一段資料的時候,它會依據以下的呼叫,通知網路執行緒:
bufferevent_write-->evbuffer_add-->evbuffer_invoke_callbacks_-->bufferevent_socket_evtbuf_cb_-->bufferevent_add_event_-->event_add-->event_add_nolock_-->evthread_notify_base
以上便是libevent跨執行緒喚醒的邏輯。
乙個多執行緒程式
這個程式會讓你深刻的體會時間片,執行緒的優先順序!建議多測試這個程式,一定要看下面的注。using system using system.collections.generic using system.text using system.threading namespace 乙個多執行緒程式 ...
乙個多執行緒程式
這個程式會讓你深刻的體會時間片,執行緒的優先順序!建議多測試這個程式,一定要看下面的注。using system using system.collections.generic using system.text using system.threading namespace 乙個多執行緒程式 ...
乙個多執行緒程式
這個程式會讓你深刻的體會時間片,執行緒的優先順序!建議多測試這個程式,一定要看下面的注。using system using system.collections.generic using system.text using system.threading namespace 乙個多執行緒程式 ...