Libevent R4: Working with events: Difference between revisions
No edit summary |
No edit summary |
||
Line 269: | Line 269: | ||
Here’s the same example as above, but instead of relying on the size of struct event from event_struct.h, we use event_get_struct_size() to use the correct size at runtime. | Here’s the same example as above, but instead of relying on the size of struct event from event_struct.h, we use event_get_struct_size() to use the correct size at runtime. | ||
* Example | |||
<source lang=c> | |||
#include <event2/event.h> | |||
#include <stdlib.h> | |||
/* When we allocate an event_pair in memory, we'll actually allocate | |||
* more space at the end of the structure. We define some macros | |||
* to make accessing those events less error-prone. */ | |||
struct event_pair { | |||
evutil_socket_t fd; | |||
}; | |||
/* Macro: yield the struct event 'offset' bytes from the start of 'p' */ | |||
#define EVENT_AT_OFFSET(p, offset) \ | |||
((struct event*) (((char*)(p)) + (offset)) | |||
/* Macro: yield the read event of an event_pair */ | |||
#define READEV_PTR(pair) \ | |||
EVENT_AT_OFFSET((pair), sizeof(struct event_pair)) | |||
/* Macro: yield the write event of an event_pair */ | |||
#define WRITEEV_PTR(pair) \ | |||
EVENT_AT_OFFSET((pair), sizeof(struct event_pair) + event_get_struct_event_size()) | |||
/* Macro: yield the actual size to allocate for an event_pair */ | |||
#define EVENT_PAIR_SIZE() \ | |||
(sizeof(struct event_pair) + 2 * event_get_struct_event_size()) | |||
void readcb(evutil_socket_t, short, void *); | |||
void writecb(evutil_socket_t, short, void *); | |||
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd) | |||
{ | |||
struct event_pair *p = malloc(EVENT_PAIR_SIZE()); | |||
if (!p) return NULL; | |||
p->fd = fd; | |||
event_assign(READEV_PTR(p), base, fd, EV_READ|EV_PERSIST, readcb, p); | |||
event_assign(WRITEEV_PTR(p), base, fd, EV_WRITE|EV_PERSIST, writecb, p); | |||
return p; | |||
} | |||
</source> | |||
== Making events pending and non-pending == | |||
한번 이벤트를 생성하면, 이벤트를 루프에 추가해서 pending 상태로 만들기 전까지는 아무것도 하지 않는다. 루프에 추가하기 위해서는 event_add() 를 사용한다. | |||
* Interface | |||
<source lang=c> | |||
int event_add(struct event *ev, const struct timeval *tv); | |||
</source> | |||
event_add() 를 호출하면 이벤트를 event_base 루프에 추가해서 pending 상태로 만들 수 있다. 성공시 0, 실패시 -1을 리턴한다. timeout 을 지정하고 싶지 않다면, tv를 NULL 로 입력하면 된다. timeout은 마이크로 초 단위까지 설정할 수 있다. | |||
만약 이미 pending 상태인 이벤으를 다시 event_add()로 추가한다면, pending 상태가 해제되고, 새로 설정된 timout 으로 다시 스케쥴링 된다. 만약 timeout 을 NULL 로 설정하여 재추가하게 되면, 아무런 일도 일어나지 않는다. | |||
* Note | |||
tv 변수에 timeout 목표 시간을 입력하면 안된다. 예를 들어 "tv->tv_sec = time(NULL)+10;"이라고 입력했다면 입력한 시간으로부터 10초 뒤가 아닌, 40년 뒤에 타이머가 작동될 것이다. | |||
* Interface | |||
<source lang=c> | |||
int event_remove_timer(struct event *ev); | |||
</source> | |||
드디어 타이머 이벤트를 삭제할 수 있는 함수가 나왔다. 만약 이벤트가 이벤트가 timeout pending 상태이며, IO 컴포넌트, 시그널 컴포넌트가 없는 상태일 때 사용가능하며, event_del() 함수와 같은 역할을 수행한다. 성공시 0, 실패시 -1을 리턴한다. | |||
== Events with priorities == | |||
여러개의 이벤트들이 같이 trigger 되었을 때, Libevent는 어떤 콜백이 먼저 수행되어야 하는지 정해진 규칙이 없다. 하지만 중요한 콜백들이 실행 우선권을 가질 숭 씨도록 우선 순위를 지정할 수 있다. | |||
앞서 설명했듯이 event_base는 하나 이상의 우선순위를 설정할 수 있고, 초기화 직후, 이벤트를 추가하기 전에 설정 가능하다. | |||
* Interface | |||
<source lang =c> | |||
int event_priority_set(struct event *event, int priority); | |||
</source> | |||
이벤트에 부여 가능한 우선순위 번호는 0부터 event_base 에 설정한 우선순위 갯수 - 1 까지이다. 성공시 0, 실패시 -1을 리턴한다. | |||
여러개의 우선순위로 이루어진 이벤트들이 동작할때, 낮은 우선 순위를 가진 이벤트는 동작하지 않는다. 대신에, Libevent는 높은 우선 순위를 가진 이벤트를 실행시킨다. 그리고 다시 이벤트를 검색한다. active 상태인 높은 우선순위 이벤트가 없을때 낮은 우선순위의 이벤트를 실행시킨다. | |||
* Example | |||
<source lang=c> | |||
#include <event2/event.h> | |||
void read_cb(evutil_socket_t, short, void *); | |||
void write_cb(evutil_socket_t, short, void *); | |||
voie main_loop(evutil_socket_t fd) | |||
{ | |||
struct event *importatn, *unimportant; | |||
struct event_base *base; | |||
base = event_base_new(); | |||
event_base_priority_init(base, 2); | |||
/* Now base has priority 0, and priority 1 */ | |||
important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); | |||
unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL); | |||
event_priority_set(important, 0); | |||
event_priority_set(unimportant, 1); | |||
/* Now, whenever the fd is ready for writing, the write callback will | |||
* happen before the read callback. The read callback won't happen at | |||
* all until the write callback is no longer active. */ | |||
} | |||
</source> | |||
별도로 우선순위를 설정하지 않았다면, 기본값으로 event_base 에서 사용가능한 우선순위 갯수 / 2 가 된다. | |||
== Inspecting event status == | |||
이벤트가 추가됐는지 혹은 이벤트가 어떤 상태인지 알고 싶을 때가 있다. | |||
Revision as of 10:25, 30 January 2015
Overview
원문은 이곳<ref>http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html</ref>에서 확인할 수 있다.
Libevent의 기본 실행 단위는 event(이벤트)이다. 모든 이벤트는 상태를 가지게 되는데 이는 다음과 같다.
- 파일 디스크립터의 read/write 준비 완료
- 파일 디스크립터의 read/write 준비 중(Egde-triggered IO Only)
- timout 만료
- signal 발생
- 사용자-정의 trigger
이벤트들은 비슷한 주기를 가지게 되는데, 한번 이벤트를 생성하게 되면 initialized 상태가 된다. 이후 event_base 에 등록하게 되면 pending 상태가 된다. 이벤트가 pending 상태에서 특정 조건을 만족하여 trigger(readable/writable, timeout expire) 되게 되면, active 상태가 되면서 등록된 callback 함수가 실행된다. 만약 이벤트가 persistent 로 설정되었다면, 다시 pending 상태로 돌아간다. 만약 persistent 가 아니라면, pending으로 돌아가지 않는다. 이벤트 delete 를 함으로써 pending 상태에서 non-pending 상태로 바꿀 수 있으며, add 를 이용해서 non-pending 에서 pending으로 변경할 수도 있다.
Constructing event objects
새로운 이벤트를 생성하기 위해서는 event_new() 인터페이스를 사용하면 된다.
- Interface
<source lang=c>
- define EV_TIMEOUT 0x01
- define EV_READ 0x02
- define EV_WRITE 0x04
- define EV_SIGNAL 0x08
- define EV_PERSIST 0x10
- define EV_ET 0x20
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
struct event *event_new(struct event_base *base,
evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
void event_free(struct event *event); </source>
event_new() 함수는 같이 넘겨지는 인자 *base 에서 동작하는 이벤트를 생성한다. 그리고 what 인자는 flag 셋인데, 사용가능한 flag 가 위쪽에 나와있다. 만약 fd 가 양수 값으로 설정됐다면, read/write 하고자 하는 파일 디스크립터 값으로 간주된다. 이벤트가 active 되면, 이벤트는 여기서 등록된 cb 함수를 다음의 인자 값과 함께 실행시킨다(fd : 파일 디스크립터, arg: 이벤트 생성시 넘겨준 arg 값)
오류 발생시, event_new() 는 NULL 을 리턴한다.
모든 이벤트는 생성 직후 initialized 상태 혹은 non-pending 상태가 된다. pedning 상태로 만들기 위해서는 event_add() 를 호출해야 한다.(아래에서 설명함)
이벤트를 삭제(메모리 해제)하고 한다면 event_free() 를 사용해야 하며, 해당 이벤트가 pending/active 상태 중에서도 사용 가능하다.(해당 이벤트가 non-pending/inactive 상태일 때 삭제된다)
- Example
<source lang=c>
- include <event2/event.h>
void cb_func(evutil_socket_t fd, short what, void *arg) {
const char *data = arg; printf("Got an event on socket %d:%s%s%s%s [%s]", (int) fd, (what & EV_TIMEOUT)? " timeout" : "", (what & EV_READ)? " read" : "", (what & EV_WRITE)? " write" : "", (what & EV_SIGNAL)? " signal" : "", data);
}
void main_loop(evutil_socket_t fd1, evutil_socket_t fd2) {
struct event *ev1, *ev2; struct timeval five_seconds = {5, 0}; struct event_base *base = event_base_new(); /* The caller has already set up fd1, fd2 somehow, and make them * nonblocking. */ ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func, (char*)"Reading event"); ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func, (char*)"Writing event"); event_add(ev1, &five_seconds); event_add(ev2, NULL); event_base_dispatch(base);
} </source>
The event flags
- EV_TIMEOUT
- 같이 설정한 시간뒤에 active 된다.
- EV_READ
- 등록한 파일 디스크립터가 Read 가능한 상태일 때 active 된다.
- EV_WRITE
- 등록한 파일 디스크립터가 Write 가능한 상태일 때 active 된다.
- EV_SIGNAL
- 등록한 시그널이 감지될 때 active 된다.
- EV_PERSIST
- 해당 이벤트는 "영구적"임을 나타낸다.
- EV_ET
- event_base 의 백엔드가 Edge-trigger 를 지원할 때, 해당 이벤트가 Edge-triggered 를 사용한다는 것을 나타낸다. EV_READ/EV_WRITE 의 Edge-trigger 버전이다.
About Event Persistance
기본적으로 이벤트가 pending 상태에서 active 상태가 되면, 등록된 callback 함수가 실행된 직후, non-pending 상태가 된다. 따라서 만약 해당 이벤트를 다시금 pending 상태로 만들고 싶다면, 콜백 함수 내에서 event_add() 통해 다시금 이벤트를 등록시켜주어야 한다.
하지만 만약 EV_PERSIST 플래그가 세팅되어 있다면, 이벤트는 영구적이 된다. 무슨 말이냐면, 이벤트가 active 상태가 되어 콜백을 실행시킨 이후에 다시 pending 상태로 돌아온다는 뜻이다. 만약 non-pending 상태로 만들고 싶다면, event_del() 를 사용하면 된다.
timeout과 persistent 옵션을 같이 사용하게 되면, timout 와 trigger 를 공유하는 형태가 된다. 예를 들어 EV_READ|EV_PERSIST 와 timeout 5초를 함께 설정하게 되면 다음과 같이 된다.
- 소켓이 read 가능하면 active된다.
- 마지막으로 active 된 지 매 5 초 후마다 active 된다.
Creating an event as its own callback argument
때때로, 콜백 함수 자신을 인자값으로 처리해야 하는 경우가 있을 수 있다. 하지만 이벤트 자체를 그냥 포인터로 넘겨줄 수는 없다. 왜냐하면 아직 생성되지 않았기 때문이다. 실제로 이벤트가 active 되고 실행하는 순간 callback 메모리가 할당되기 때문이다. 따라서 이를 위해서는 별도의 event_self_cbarg() 함수를 사용해야 한다.
- Interface
<source lang=c> void *event_self_arg(); </source>
event_self_cbarg()는 콜백 함수를 가리키는 "매직 포인터"를 리턴해준다.
- Example
<source lang=c>
- include <event2/event.h>
static int n_calls = 0;
void cb_func(evutil_socket_t fd, short what, void *arg) {
struct event *me = arg; printf("cb_func called %d times so far.\n", ++n_calls); if(n_calls > 100) { event_del(me); }
}
void run(struct event_base *base) {
struct timeval one_sec = {1, 0}; struct event *ev; /* We're going to set up a repeating timer to get called called 100 * times. */ ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg()); event_add(ev, &one_sec); event_base_dispatch(base);
} </source> event_new(), evtimer_new(), evsignal_new(), event_assign(), evtimer_assign, evsignal_assign() 등과 함께 사용할 수 있다.
Timeout-only events
evtimer_ 로 시작하는 매크로 들이 있다. event_ 로 시작하는 함수와 timer 관련 옵션을 함께 설정할때 사용가능한데, 이를 사용하면 번거로운 작업을 줄일 수 있다.
- Interface
<source lang=c>
- define evtimer_new(base, callback, arg) \
event_new(base), -1, 0, (callback), (arg))
- define evtimer_add(ev, tv) \
event_add((ev), (tv))
- define evtimer_Del(ev) \
event_del(ev)
- define evtimer_pending(ev, tv_out) \
event_pending((ev), EV_TIMEOUT, (tv_out))
</source>
Constructing signal events
Libevent는 POSIX 시그널도 감지가 가능하다.
- Interface
<source lang=c>
- define evsignal_new(base, signum, cb, arg) \
event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
</source> event_new 사용법과 같은데, 파일 디스크립터 대신 시그널 번호만 넘겨주는 것이 다르다.
- Example
<source lang=c> struct event *hup_event; struct event_base *base = event_base_new();
/* call sighup_function on a HUP signal */ hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL); </source> 등록한 시그널 콜백은 시그널이 발생될때 이벤트 루프에서 호출된다. 따라서 시그널 콜백 사용시, POSIX 시그널 핸들러에 아무처리를 하지않도록 따로 설정해놓는 것이 안전하다.
signal event 등록은 위한 매크로 역시 존재한다.
- Interface
<source lang=c>
- define evsignal_add(ev, tv) \
event_add((ev), (tv))
- define evsignal_del(ev) \
event_del(ev)
- define evsignal_pending(ev, what, tv_out) \
event_pending((ev), (what), (tv_out))
</source>
- Caveats when working with signals
- Libevent 에서 시그널을 등록해서 사용할 때 주의해야할 점이 있다. 프로세스당 오직 하나의 event_base 에서만 시그널을 수신할 수 있다는 것이다. 만약 하나의 프로세스에서 여러개의 event_base 를 생성하고 각각의 event_base 마다 시그널을 등록했을 경우, 오직 하나의 event_base 에서만 시그널을 수신하게 된다는 것이다. 시그널의 번호가 모두 달라도 안된다.
- 단, kqueue 에서는 제약이 없다.
Setting up events without heap-allocation
성능, 혹은 다른 이유로 힙영역 이외의 다른 곳에 이벤트 메모리를 관리할 수 있다. 이 기능을 사용하게 되면 다음의 문제들을 관리할 수 있다.
- 작은 양의 메모리를 heap 영역에 할당하게 되면 오버헤드가 발생한다.
- struct event pointer 를 역 조회하게되면 time overhead 가 발생한다.
- 만약 이벤트가 캐시에 없는 경우, 이를 위한 time-overhead 가 발생할 수 있다.
이를 위해서는 event_assign() 을 사용하면 된다. 하지만 이를 사용하게 되면 다른 버전의 Libevent 와의 바이너리 호환성을 깨트릴 수 있다. 왜냐하면 이벤트 구조체의 사이즈가 달라질 수 있기 때문이다.
위에 나열한 문제들은 굉장히 작은 문제들이다. 그리고 거의 대부분의 어플리케이션에서는 문제되지 않는다. 이벤트 메모리를 다른 곳에 관리함으로써 생기는 문제점들에 대해 정확히 알고있지 않은 한, event_new() 를 사용하길 바란다. event_assign()을 사용하게 되면 다른 버전의 Libevent(만약 이벤트 구조체 크기가 달라졌다면)와 연동했을 경우, 에러를 찾기 힘들어진다.
- Interface
<source lang=c> int event_assign(struct event *event,
struct event_base *base, evutil_socket_t fd, short what, void (*callback)(evutil_socket_t, short, void *), void *arg);
</source> event 인자값을 제외한, 모든 인자값은 event_new() 에서 사용하는 것과 같다. 성공시 0, 실패시 -1을 리턴한다.
- Example
<source lang=c>
- include <event2/event.h>
/* Watch out! Including event_struct.h means that your code will not
* be binary-compatible with future versions of Libevent. */
- include <event2/event_struct.h>
- include <stdlib.h>
struct event_pair {
evutil_socket_t fd; struct event read_event; struct event write_event;
}; void readcb(evutil_socket_t, short, void *); void writecb(evutil_socket_t, short, void *); struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd) {
struct event_pair *p = malloc(sizeof(struct event_pair)); if(!p) return NULL; p->fd = fd; event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p); event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p); return p;
} </source>
- Warning
절대로 pending 중인 이벤트에 대해 event_assign() 를 사용하면 안되다. 만약 그런 일을 하게되면 정말로 오류가 발생해도 원인을 찾을 수 없게 된다. 만약 이미 Initialized 됐고, pending 상태의 이벤트라면, event_del() 을 먼저 사용하고, event_assign()을 사용해야 한다.
assign 과 관련된 매크로 함수들 역시 존재한다.
- Interface
<source lang=c>
- define evtimer_assign(event, base, callback, arg) \
event_assign(event, base, -1, 0, callback, arg)
- define evsignal_assign(event, base, signum, callback, arg) \
event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback arg)
</source>
event_assign() 을 사용하면서도 추후의 Libevent 라이브러리와 바이너리 호환성을 유지해야 한다면, 사용중인 라이브러리의 struct event 크기를 확인할 수 있다.
- Interface
<source lang=c> size_t event_get_struct_event_size(void); </source>
이 함수는 현재 사용중인 이벤트 구조체의 바이트 크기를 리턴한다. This function returns the number of bytes you need to set aside for a struct event. As before, you should only be using this function if you know that heap-allocation is actually a significant problem in your program, since it can make your code much harder to read and write.
Note that event_get_struct_event_size() may in the future give you a value smaller than sizeof(struct event). If this happens, it means that any extra bytes at the end of struct event are only padding bytes reserved for use by a future version of Libevent.
Here’s the same example as above, but instead of relying on the size of struct event from event_struct.h, we use event_get_struct_size() to use the correct size at runtime.
- Example
<source lang=c>
- include <event2/event.h>
- include <stdlib.h>
/* When we allocate an event_pair in memory, we'll actually allocate
* more space at the end of the structure. We define some macros * to make accessing those events less error-prone. */
struct event_pair {
evutil_socket_t fd;
};
/* Macro: yield the struct event 'offset' bytes from the start of 'p' */
- define EVENT_AT_OFFSET(p, offset) \
((struct event*) (((char*)(p)) + (offset))
/* Macro: yield the read event of an event_pair */
- define READEV_PTR(pair) \
EVENT_AT_OFFSET((pair), sizeof(struct event_pair))
/* Macro: yield the write event of an event_pair */
- define WRITEEV_PTR(pair) \
EVENT_AT_OFFSET((pair), sizeof(struct event_pair) + event_get_struct_event_size())
/* Macro: yield the actual size to allocate for an event_pair */
- define EVENT_PAIR_SIZE() \
(sizeof(struct event_pair) + 2 * event_get_struct_event_size())
void readcb(evutil_socket_t, short, void *); void writecb(evutil_socket_t, short, void *); struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd) {
struct event_pair *p = malloc(EVENT_PAIR_SIZE()); if (!p) return NULL; p->fd = fd; event_assign(READEV_PTR(p), base, fd, EV_READ|EV_PERSIST, readcb, p); event_assign(WRITEEV_PTR(p), base, fd, EV_WRITE|EV_PERSIST, writecb, p); return p;
} </source>
Making events pending and non-pending
한번 이벤트를 생성하면, 이벤트를 루프에 추가해서 pending 상태로 만들기 전까지는 아무것도 하지 않는다. 루프에 추가하기 위해서는 event_add() 를 사용한다.
- Interface
<source lang=c> int event_add(struct event *ev, const struct timeval *tv); </source>
event_add() 를 호출하면 이벤트를 event_base 루프에 추가해서 pending 상태로 만들 수 있다. 성공시 0, 실패시 -1을 리턴한다. timeout 을 지정하고 싶지 않다면, tv를 NULL 로 입력하면 된다. timeout은 마이크로 초 단위까지 설정할 수 있다.
만약 이미 pending 상태인 이벤으를 다시 event_add()로 추가한다면, pending 상태가 해제되고, 새로 설정된 timout 으로 다시 스케쥴링 된다. 만약 timeout 을 NULL 로 설정하여 재추가하게 되면, 아무런 일도 일어나지 않는다.
- Note
tv 변수에 timeout 목표 시간을 입력하면 안된다. 예를 들어 "tv->tv_sec = time(NULL)+10;"이라고 입력했다면 입력한 시간으로부터 10초 뒤가 아닌, 40년 뒤에 타이머가 작동될 것이다.
- Interface
<source lang=c> int event_remove_timer(struct event *ev); </source> 드디어 타이머 이벤트를 삭제할 수 있는 함수가 나왔다. 만약 이벤트가 이벤트가 timeout pending 상태이며, IO 컴포넌트, 시그널 컴포넌트가 없는 상태일 때 사용가능하며, event_del() 함수와 같은 역할을 수행한다. 성공시 0, 실패시 -1을 리턴한다.
Events with priorities
여러개의 이벤트들이 같이 trigger 되었을 때, Libevent는 어떤 콜백이 먼저 수행되어야 하는지 정해진 규칙이 없다. 하지만 중요한 콜백들이 실행 우선권을 가질 숭 씨도록 우선 순위를 지정할 수 있다.
앞서 설명했듯이 event_base는 하나 이상의 우선순위를 설정할 수 있고, 초기화 직후, 이벤트를 추가하기 전에 설정 가능하다.
- Interface
<source lang =c> int event_priority_set(struct event *event, int priority); </source> 이벤트에 부여 가능한 우선순위 번호는 0부터 event_base 에 설정한 우선순위 갯수 - 1 까지이다. 성공시 0, 실패시 -1을 리턴한다.
여러개의 우선순위로 이루어진 이벤트들이 동작할때, 낮은 우선 순위를 가진 이벤트는 동작하지 않는다. 대신에, Libevent는 높은 우선 순위를 가진 이벤트를 실행시킨다. 그리고 다시 이벤트를 검색한다. active 상태인 높은 우선순위 이벤트가 없을때 낮은 우선순위의 이벤트를 실행시킨다.
- Example
<source lang=c>
- include <event2/event.h>
void read_cb(evutil_socket_t, short, void *); void write_cb(evutil_socket_t, short, void *);
voie main_loop(evutil_socket_t fd) {
struct event *importatn, *unimportant; struct event_base *base; base = event_base_new(); event_base_priority_init(base, 2); /* Now base has priority 0, and priority 1 */ important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL); unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL); event_priority_set(important, 0); event_priority_set(unimportant, 1); /* Now, whenever the fd is ready for writing, the write callback will * happen before the read callback. The read callback won't happen at * all until the write callback is no longer active. */
} </source>
별도로 우선순위를 설정하지 않았다면, 기본값으로 event_base 에서 사용가능한 우선순위 갯수 / 2 가 된다.
Inspecting event status
이벤트가 추가됐는지 혹은 이벤트가 어떤 상태인지 알고 싶을 때가 있다.
References
<references />