Libevent R2: Getting an event base

From 탱이의 잡동사니
Jump to navigation Jump to search

Overview

원문은 이곳<ref>http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html</ref>에서 확인할 수 있다.

Libevent 를 사용하기전, 하나 혹은 그 이상의 event_base_structure를 생성해야 한다. 각각의 event_base_structure 는 설정된 이벤트를 감시하며, Poll 한다.

만약 event_base 가 Locking 을 사용한다면 멀티 스레드 환경에서도 사용이 가능하지만, 만약 사용하지 않는다면 반드시 싱글 스레드 환경에서만 사용해야 한다. 그리고 멀티 스레드 IO polling 을 하고자 한다면, 각각의 스레드마다 event_base 를 생성해 주어야 한다.

각각의 event_base는 어떤 이벤트들이 준비됐는지 확인하기 위해 "메소드" 혹은 "백엔드"를 사용한다. 다음과 같은 메소드들이 있다.

  • select
  • poll
  • epoll
  • kqueue
  • devpoll
  • evport
  • win32

사용자는 환경 변수를 통해서 특정 백엔드/메소드 를 비활성화 시킬 수 있다. 예를 들어 kqueue 를 비활성화 시키고 싶다면 EVENT_NOKQUEUE 환경변수를 설정하면 된다. 만약 프로그램 내에서 특정 백엔드/메소드를 비활성화 시키고 싶다면 event_config_avoid_method() 를 사용하면 된다.

Setting up a default event_base

event_base_new() 함수는 새로운 event base를 기본 설정상태로 생성하고 리턴한다. 환경 변수를 검사하고 새로 생성한 event_base 포인터를 넘겨주는데, 만약 에러가 발생하면 NULL을 리턴한다.

만약 여러 개의 메소드가 있다면, 동작 OS 에서 가장 빨리 작동하는 메소드를 자동으로 선택한다.

  • Interface

<source lang=c> struct event_base *event_base_new(void); </source>

대부분의 프로그램에서는 이것만으로도 충분할 것이다. event_base_new() 함수는 <event2/event.h> 파일에 선언되어 있으며, Libevent 1.4.3 이후부터 사용 가능하다.

Setting up a complicated event_base

만약 event_base 에 대해 더 세밀한 설정을 하고 싶다면 event_config 를 사용할 수 있다. event_config 에 원하는 내용을 설정하고, event_base 생성시 인자값으로 사용할 수 있다.

  • Interface

<source lang=c> struct event_config *event_config_new(void); struct event_base *event_base_new_with_config(const struct event_config *cfg); void event_config_free(struct event_config *cfg); </source>

위 함수들을 event_base 와 함께 사용하고자 한다면 먼저 event_config_new() 를 사용해 event_config 를 생성하고, 다른 함수들을 통해 event_config 를 원하는 내용으로 설정한다. 그 후에, event_base_new_with_config() 함수와 함께 사용하여 event_base 를 생성하면 된다. 그리고, 사용이 끝난 event_config 는 event_config_free() 로 해제하면 된다.

  • Interface

<source lang=c> int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {

   EV_FEATURE_ET = 0x01,
   EV_FEATURE_O1 = 0x02,
   EV_FEATURE_FDS = 0x04,

}; int event_config_require_features(struct event_config *cfg,

                                 enum event_method_feature feature);

enum event_base_config_flag {

   EVENT_BASE_FLAG_NOLOCK = 0x01,
   EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
   EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
   EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
   EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
   EVENT_BASE_FLAG_PRECISE_TIMER = 0x20

}; int event_config_set_flag(struct event_config *cfg,

   enum event_base_config_flag flag);

</source> event_config_avoid_method() 를 사용하면 Libevent 에서 메소드 사용시 특정 메소드를 사용하지 말라고 "이름"으로 지정할 수 있다. 그리고 event_config_require_feature() 를 사용하면 메소드가 어떤 기능들을 가져야 하는지를 명시하여 조건에 부합되지 않는 메소드는 사용하지 않도록 할 수 있다. event_config_set_flag() 를 사용하면 run-time 중에 event base 생성시 어떤 조건으로 생성해야 하는지를 지정할 수 있다.

event_config_require_feature() 에서 사용가능한 항목은 다음과 같다.

  • EV_FEATURE_ET
백엔드는 Edge-Triggered IO 를 지원해야 한다.
  • EV_FEATURE_O1
백엔드는 싱글 이벤트 추가/삭제/동작 시, O(1) 복잡도를 가져야 한다.
  • EV_FEATURE_FDS
백엔드는 소켓뿐만이 아닌 다른 타입의 file descriptor 를 지원해야 한다.

event_config_set_flag() 에서 사용가능한 항목은 다음과 같다.

  • EVENT_BASE_FLAG_NOLOCK
event_base에 Lock을 사용하지 말 것. 이 옵션을 사용하면 event_base 에서의 Locking/Release 시간을 줄일 수 있다. 하지만 멀티 스레드 환경에서의 동작은 보장하지 않는다.
  • EVENT_BASE_FLAG_IGNORE_ENV
EVENT_* 형식의 환경변수를 체크하지 말 것. 백엔드 메소드 선택시, 환경변수를 고려하지 않고 선택한다. 이 옵션을 사용할 땐 심사숙고 하기 바란다. 프로그램과 Libevent 사이의 인터액션에 영향을 주기 때문에 디버그가 힘들어진다.
  • EVENT_BASE_FLAG_NO_CACHE_TIME
매번 Timeout 을 직접 측정한다. CPU 를 많이 사용하게 될지도 모른다. 사용시 조심할 것.
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
Tells Libevent that, if it decides to use the epoll backend, it is safe to use the faster "changelist"-based backend. The epoll-changelist backend can avoid needless system calls in cases where the same fd has its status modified more than once between calls to the backend’s dispatch function, but it also trigger a kernel bug that causes erroneous results if you give Libevent any fds cloned by dup() or its variants. This flag has no effect if you use a backend other than epoll. You can also turn on the epoll-changelist option by setting the EVENT_EPOLL_USE_CHANGELIST environment variable.
  • EVENT_BASE_FLAG_PRECISE_TIMER
기본값으로 Libevent는 OS 시스템에서 제공하는 Timing 방식 중, 가장 빠른 Timing 방식을 선택하려고 한다. 그런데 만약 속도는 조금 늦더라도 보다 더 정확한 Timing 방식이 필요하다면 이 옵션을 설정하면 된다. 하지만 OS 에서 이과 같은 Timing 방식을 지원하지 않는다면 이 옵션은 무시된다.

위에 설명한 함수 모두 성공시 0, 실패시 -1을 리턴한다.

  • Note
event_config 를 설정할 때, 설정사항을 만족하지 못하는 메소드를 찾기가 그리 어렵지 않다. 예를 들면 Libevent 2.0.1-alpha 버전을 사용할 때, 윈도우즈에서 O(1)을 만족하는 백엔드는 없다. 그리고 리눅스에서 EV_FEATURE_FDS 와 EV_FEATURE_O1을 동시에 만족하는 백엔드는 없다. 만약 Libevent에서 설정 사항을 만족하는 메소드를 찾지 못할 경우, NULL 을 리턴한다.
  • Interface

<source lang=c> int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus) </source> 이 함수는 현재는 윈도우즈에서 IOCP를 사용할 때만 사용할 수 있다. 이 함수를 사용하여 event_config 를 설정하면, 후에 event_base 를 생성할 때, 몇개의 CPU 를 멀티 스레딩으로 사용할 지를 지정할 수 있다. 절대적인 숫자는 아니며, 단지 기준일 뿐이다. 실제 동작시, 이보다 더 적은 혹은 더 많은 CPU가 사용될 수 있다.

  • Interface

<source lang=c> int event_config_set_max_dispatch_interval(struct event_config *cfg,

   const struct timeval *max_interval, 
   int max_callbacks,
   int min_priority);

</source> This function prevents priority inversion by limiting how many low-priority event callbacks can be invoked before checking for more high-priority events. If max_interval is non-null, the event loop checks the time after each callback, and re-scans for high-priority events if max_interval has passed. If max_callbacks is nonnegative, the event loop also checks for more events after max_callbacks callbacks have been invoked. These rules apply to any event of min_priority or higher.

  • Example: Perferring edge-triggered backends

<source lang=c> struct event_config *cfg struct event_base *base; int i;

/* My program watns to use edge-triggered events if at all possible. So

* I'll try to get a base twice: Once insisting on edge-triggered IO, and
* once not. */

for(i = 0; i < 2; ++i) {

   cfg = event_config_new();
   
   /* I don't like select */
   event_config_avoid_method(cfg, "select");
   
   if(i == 0)
   {
       event_config_require_features(cfg, EV_FEATURE_ET);
   }
   
   base = event_base_new_with_config(cfg);
   event_config_free(Cfg);
   if(base)
   {
       break;
   }
   
   /* If we get here, event_base_new_with_config() returned NULL. If
   * this is the first time around the loop, we'll try again without
   * setting EV_FEATURE_ET. If this is the seconds time around the
   * loop, we'll give up. */

} </source>

  • Example: Avoiding priority-inversion

<source lang=c> struct event_config *cfg; struct event_base *base;

cfg = event_config_new() if(!cfg)

   /* Handle error */;

/* I'm going to have events running at two prioirities. I expect that

* some of my priority-1 events are going to have pretty slow callbacks,
* so I don't want more than 100 msec to elapse (or 5 callbacks) before
* checking for priority-0 events. */

struct timeval msec_100 = {0, 100*1000}; event_config_set_max_dispatch_interval(cfg, &msec_100, 5, 1);

base = event_base_new_with_config(cfg); if(!base)

   /* Handle error */;

event_base_priority_init(base, 2); </source>

Examing an event_base's backend method

현재 사용중인 메소드에서 어떤 기능을 지원하는지, 어떤 메소드를 사용하는지 역시 확인이 가능하다.

  • Interface

<source lang=c> const char **event_get_supported_methods(void); </source>

event_get_supported_methods() 는 해당 버전의 Libevent 에서 사용가능한 메소드 이름을 문자열로 리턴한다. 마지막 부분은 NULL 로 표시한다.

  • Example

<source lang=c> int i; const char **methods = event_get_supported_methods(); printf("Starting Libevent %s. Available methods are:\n", event_get_version());

for(i = 0; methods[i] != NULL; ++i) {

   printf("    %s\n", methods[i]);

} </source>

  • Note
이 함수를 사용하면 Libevent를 컴파일했을 때 메소드 리스트를 리턴하는데, 실제 Run-time 때의 내용과 다를 수가 있다.
  • Interface

<source lang=c> const char *event_base_get_method(const struct event_base *base); enum event_method_feature event_base_get_features(const struct event_base *base); </source>

event_base_get_method() 를 사용하면 현재 event_base() 에 의해서 사용중인 메소드의 이름을 리턴한다. event_base_get_features() 는 사용가능한 기능을 Bitmask 형태로 리턴한다.

  • Example

<source lang=c>

  1. include <stdio.h>
  2. include <event2/event.h>

int main(int argc, char **argv) {

   struct event_base *base;
   enum event_method_feature f;
   
   base = event_base_new();
   if(!base)
   {
       puts("Could not get an event_base");
       return 0;
   }
   
   printf("Using Libevent with backend method %s.\n", event_base_get_method(base));
   f = event_base_get_features(base);
   if((f & EV_FEATURE_ET))
   {
       printf("   Edge-triggered events are supported.\n");
   }
   if((f & EV_FEATURE_O1))
   {
       printf("   O(1) event notification is supported.\n");
   }
   if((f & EV_FEATURE_FDS))
   {
       printf("   All FD types are supported.\n");
   }
   
   puts("");
   

return 0; } </source>

Results.

Using Libevent with backend method epoll.
   Edge-triggered events are supported.
   O(1) event notification is supported.

Deallocating an event_base

event_base 사용이 끝났다면, event_base_free() 함수를 통해 메모리를 해제해주어야 한다. <source lang=c> void event_base_free(struct event_base *base); </source> 이 함수를 사용한다고 해서 event_base 와 연동된 다른 오브젝트들 까지 메모리 해제가 되는 것이 아니며, 소켓을 종료하는 것이 아니다.

Setting priorities on an event_base

Libevent 는 이벤트에 여러개의 우선 순위를 설정할 수 있다. 기본값으로, event_base는 하나의 우선 순위 레벨만 설정 가능하다. event_base_priority_init() 함수를 통해 우선순위 를 설정할 수 있다.

  • Interface

<source lang=c> int event_base_priority_init(struct event_base *base, int n_priorities); </source> 성공시 0, 실패시 -1을 리턴한다. 설정하고자 하는 event_base와 사용하고자 하는 우선순위의 갯수를 인자로 입력하면 된다. 최소 1 이상의 숫자를 입력해야 하며, 사용가능한 우선순위 레벨은 0부터(가장 중요함), 설정한 우선순위 갯수 -1(가장 안 중요함)까지 설정할 수 있다.

최대 설정 가능한 숫자는 EVENT_MAX_PRIORITIES 로 지정되어 있으며, 이 숫자보다 더 많은 숫자로 지정하려고 하면, 에러를 리턴한다.

  • Note
이 함수를 사용시, 반드시 event 들이 active 되기 전에 설정해야 한다. 가장 좋은 방법은 event_base 생성과 동시에 설정하는 것이다.

현재 사용중인 event_base 에서 사용가능한 우선순위 갯수를 확인하고자 한다면, event_base_getnpriorities() 를 사용하면 된다.

  • Interface

<source lang=c> int event_base_get_npriorities(struct event_base *base); </source> 리턴값으로, 인자값으로 넘겨진 event_base 에서 사용가능한 우선순위 숫자를 돌려준다. 만약 리턴값으로 3이 넘어왔다면, 설정 가능한 우선순위 숫자는 0, 1, 2 이다.

Reinitializing an event_base after fork()

모든 이벤트 백엔드가 fork() 후에 깔끔하게 초기화되지는 않는다. 따라서 fork() 이후에도 계속해서 event_base 를 사용하고 싶다면 재초기화를 해주어야 한다.

  • Interface

<source lang=c> int event_reinit(struct event_base *base); </source> 함수 수행 성공시 0, 실패시 -1을 리턴한다.

  • Example

<source lang=c> struct event_base *base = event_base_new();

/* ... add some events to the event_base */

if(fork()) {

   /* In parent */
   continue_running_parent(base);

} else {

   /* In child */
   event_reinit(base);
   continue_running_child(base);

} </source>

References

<references />