본문 바로가기

진리는어디에

libevent and multithread

/**
  libevet 멀티 쓰레드 환경에서 사용코자 했는데, 이것이 생각 처럼 동작
하지 않았다. connection이 하나만 들어 왔을 때는 정상동작 했지만, 둘 이
상이되면서 부터는 event_dispatch() 에서 1을 리턴(set 되어 있는 이벤트
가 없다라는 의미)하면서 계속 종료  되었다.
  혹시나 싶어 libevent의 멀티 쓰레드 환경에 대해 찾아 보니 아래와 같은
글이 있어 짧은 영어 실력이나마 번역을 해 보았다.
 
  혹시나 누군가가 잘 못된 내용을 진실로 받아 들이고 그걸 다른 사람에게
진실인양 전파 한다면 세상에 잘못된 지식들이 판치게 되고, 지식을 추구하
는 사람들에게 있어 그것만큼 나쁜 일이 없다.

  아래의 글 중 잘 못된 내용이 있다면 언제든지
kukuta@gmail.com으로 알
려주면 고맙겠다.
*/


As the guy who added thread support to memcached, I feel qualified to
answer this one:

 memcached(libevent 이용해서 만든 일종의 메모리 DB)에 멀티쓰레드를
사용하려는 사람들에게 추천합니다.

What libevent doesn't support is sharing a libevent instance across
threads. It works just fine to use libevent in a multithreaded process
where only one thread is making libevent calls.

 libevent는 쓰레드간에 공유를 지원히지 않습니다. 만일 당신이 쓰레드를 써
야 하겠다면 한 쓰레드만이 libevent 호출하도록 만들어야 합니다.

What also works, and this is what I did in memcached, is to use multiple
instances of libevent. Each call to event_init() will give you back an
"event base," which you can think of as a handle to a libevent instance.
(That's documented in the current manual page, but NOT in the older
manpage on the libevent home page.) You can have multiple threads each
with its own event base and everything works fine, though you probably
don't want to install signal handlers on more than one thread.

 이것이 제가 memcached에서 libevent 인스턴스를 여러개 만들어서 사용하
려는 이유입니다. 각각 event_init() 호출은 개개별로 "event base" 객체를 생
성 합니다(이것은 요즘에 나오는 libevent man page에도 기록되어 있습니다).
 이런 방법으로 각각의 event base 가진 쓰레드를 생성 할 수 있으며, 비록
signal handler 하나의 쓰레드 이상에 인스톨 하는 것을 원치 않았더라도,
모든 것들이 순조롭게 잘 돌아 갈겁니다.

 In the case of memcached, I allocate one event base per thread at
startup time. One thread handles the TCP listen socket; when a new
request comes in, it calls accept() then hands the file descriptor to
another thread to handle from that point on -- that is, each client is
bound to a particular thread. All you have to do is call
event_base_set() after you call event_set() and before you call event_add().
 
 memcached의 경우, 나는 쓰레드의 시작점에서 event base 각 쓰레드 마다
할당 했습니다. 하나의 쓰레드가 listen 소켓을 들고, 새로운 접속이 시도 될 때
accept() 호출 하고 파일디스크립터(클라이언트소켓)을 생성하여 다른 쓰레드
에 넘깁니다. 이런 식으로 해서 각 클라이언트는 각자의 쓰레드에 바인드 됩니다.
 여기서 해야 할 일은 event_set()함수 호출 후 event_add() 호출 하기 전에
event_base_set() 호출하는 것 뿐입니다
.

Unfortunately, you pretty much have to use pipe() to communicate between
libevent threads, That's a limitation, and honestly it's a pretty big
one: it makes a master/worker thread architecture, where one thread
handles all the I/O, much less efficient than you'd like. My initial
implementation in memcached used an architecture like that and it chewed
lots of CPU time. That's not really libevent's fault -- no UNIX-ish
system I'm aware of has an equivalent to the Windows
WaitForMultipleObjects API that allows you to wake up on semaphores /
condition variables and on input from the network. Without that, any
solution is going to end up using pipes (or maybe signals, which have
their own set of issues in a multithreaded context) to wake up the
libevent threads.

 하지만 불행하게도, libevent 쓰레드간의 통신에는 pipe() 사용해야만 합니다.
그게 한계점이고, 상당히 까칠합니다. 이런 이유로 하나의 쓰레드가 모든 IO 쓰레
드들을 관리 하는 master/worker 쓰레드 아키텍쳐가 만들어 졌는데, 이게 성능에
있어서 또한 상당히 까칠 합니다. CPU 많이 잡아 먹는다는 말입니다. 이것은
결코 libevent의 결함이 아닙니다. 어떤 UNIX-ish(?) 시스템에서도 윈도우의
WaitForMutipleObject API 같이 세마포어/condition variables wake 할수 있는
기능을 제공 하지 않습니다. WaitForMultipleObject 같은 지원이 없고서는 어떠한
시도도 결국 pipe 사용하는 것으로 끝났습니다(그렇지 않다면 아마도 각각의
쓰레드 마다 발행 셋(?set of issues) 가진 signal을 이용하던지 말입니다)

-Steve

원문 보기 : http://monkeymail.org/archives/libevent-users/2007-January/000450.html
추가 사항 : 2007. 3. 9 : libevent가 사용하고 있는 epoll 라이브러리 자체는 threadsafe하다고 한다.
                http://www-gatago.com/linux/kernel/6116742.html
유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!