Thursday, June 11, 2009

boost::thread - wakup threads

지난번 포스팅에 이은 boost::thread 관련 두번째.
The second posting is following the previous one.

이번에 얘기할 건, waiting - wakeup 방식의 thread scheduling 을 boost::thread 를 이용해서 하는 부분.
What I want to say, how to use 'waiting-wakeup' type threading model by 'boost::thread'.

waiting - wakeup 은 sleep - wakeup 이라고 말도 하고 다양한 용어가 있으니 아래 그림을 보고 이해하시길.
It may cause some confusing because of the name 'waiting - wakeup'. It's also called as 'sleep - wakeup' and more. To make sure understanding, I put a drawing below.




뭐, 흔한 방법 중 하나 이니 모르시는 분은 없을 듯. :) 대표적인 건 Open MP.
This is one of the common threading models. Most programmers probably know it. :)
One of the popular things is Open MP.

boost::thread 에서 thread 간 동기화는 기본적으로 mutex 와 condition 을 이용하는데, 이 경우에는 barrier 가 도움이 많이 된다.
Generally, we use 'mutex' and 'condition' for thread synchronization in the boost::thread.
In particular, 'barrier' is helpful for this case.

기본적인 thread 동기화는 다음과 같이 이루어진다. 참고로 모든 코드는 pseudo-code 로 작성됨.
A basic routine looks like the following codes. (Note: All codes are written as pseudo-codes)


boost::mutex mutex;
boost::condition condition;
...

[in a thread]
boost::
mutex::unique_lock lock(mutex);
...
condition.wait(lock);
...
do something

unique_lock, scoped_lock 등의 결정은 그 때 상황에 맞게 잘 조절.
You can choose 'scoped_lock' instead of 'unique_lock' if you want to do.

Windows 로 보면Event 로 보면 되고, Linux 에는 뭐 이름이 거의 같으므로 패스.
You can consider it as 'Event' in Windows OS, and you can just know it in Linux because its name is almost similar with them.

그런데 이것만으로 n 개의 slave threads 를 기다렸다가 wakeup 하는 구조를 짜기에는 코드가 지저분해진다.
However, it's not enough to make 'N threads waiting - wakeup' threading model. It may spoil your codes.

그래서 Windows 에 있는 Semaphore 와 거의 같은 동작을 하는 barrier 를 쓰면 쉽게 할 수 있다.
You can make it easy by using 'barrier' which works as 'Semaphore' in Windows.


boost::scoped_ptr[boost::barrier] barrier;
barrier.reset(new boost::barrier( Number of related threads));

[in a thread]
barrier.wait();

[in the other thread]
barrier.wait();

barrier 를 생성할 때 숫자를 넣는 데 이건 이 barrier 를 사용하게 되는 thread 의 숫자이다.
You need to pass a number when you create a barrier instance, which means the number of threads will use the barrier.

즉, 두 개의 threads 에서 barrier 를 access 한 다면 2 를 넘겨준다.
In other words, you need to pass '2' value if two threads access the barrier.

그러면 barrier.wait() 가 두번째 불릴 때 모든 barrier.wait() 가 return 되게 된다.
So, when the 'barrier.wait()' function is called twice, all threads wake up.

만약 1을 넣고 barrier.wait() 를 부르면? 그냥 아무일 없었다는 듯이 return 한다.
If you passed '1' value and the call 'barrier.wait(), nothing happens. Just go without any block.

또한, 숫자를 5 를 넣어놓고 barrier.wait() 를 4번만 불렀다면, 모든 thread 는 barrier.wait() 가 한번 더 불릴 때까지 무한 대기한다.
In addition, if you set '5' value and have called 'barrier.wait()' four times, all thread still be in waiting state until one more calling of 'barrier.wait()'.


Main thread 1개와 Slave thread 3 개를 위의 waiting - wakeup 방식으로 개발해보자.
Let's do it with one main thread and three slave threads.


SlaveThread::boost::mutex m_mutex;
SlaveThread::boost::condition m_condition;
void
SlaveThread::WakeUp() { m_condition.notify_one(); }
MainThread::boost::barrier m_barrier;

[in the main thread]
main loop
{
... // Do something
...
for_each(
WakeUp(threads) ); // Wake up all slaves
...
m_barrier.wait(); // Wait for slaves.
...
... // Do something
}

[in a slave thread]
thread loop
{
scoped_lock lock(m_
mutex);
m_condition.wait(lock); // Wait for a signal from the main thread.
... // Waked Up!!! Do something
...
mainthread::m_barrier.wait(); // Wait for the barrier.
}

뭐 이런식으로 짤 수 있겠다.
We can do that like this way.

하지만!!! 위의 방식에는 구조적인 문제가 있다.
But!!! The codes have a structural problem.

어떤 게 문제인지 함 맞춰보시길. :)
Can you find the problem? :)

2 comments:

  1. 이거 아무도 퀴즈의 답을 안달아서 자답!!! ㅋ
    이런 방식으로 짰을 때 문제는,
    Main thread 가 slave thread 를 생성 후에도 priority 를 잡고 있다면, (선점하고 있다면)
    slave thread 가 자신의 m_condition.wait 를 부르기 전에, main thread 가 wakeup 을 불러버리는 수가 생긴다.
    그러면, slave 중 하나가 barrier wait 를 못 부르게 되기 때문에 main thread 는 영영 블럭된다.

    즉, main thread 는 slave thread 가 다 생성된 후 main loop 에 진입해야 함.

    ReplyDelete
  2. 덧붙이자면, 이 구조는 main thread 가 계속된 선점을 하여서 slave thread 가 starvation 에 들어간다면 다시한번 block 될 수 있다.
    main thread 는 slave wakeup 을 부르기 전에 그놈들이 확실히 쳐 자고 있는 지 확인을 해야한다.
    이는 condition 을 각 slave 마다 하나씩 더 생성해서 해결이 가능한데, 이렇게되면 전체적인 성능을 저하시키게 된다.

    결론:
    번거롭다면 그냥 open mp 나 tbb 를 쓰고,
    원래 구조가 multi thread 구조라면 이걸 쓰지 말고 그냥 완전 비동기 시스템을 만드는 게 낫다.
    :)

    ReplyDelete