본문 바로가기

기술자료/CPP

멀티스레드 강좌 3


The Boost.Threads Library


 


안녕하셨나요 ~ ^^;


어느덧 3번째 이야기가 되었답니다.
앞에 이야기들이 도움이 좀 되셨나요?


그리고 보니…… 별로 설명한
게 … 없군요.. 하하핫 ^^;;


읽어 주시고 저에게 많은
조언을 해주신 분들에게 감사의 말을 이 자리를 빌어 전해드립니다 ^^;


 


이번 이야기에서는 약간 복잡하고
어려운 주제를 다룰 거 같습니다.


그래서 앞의 이야기들보다도
머리가 아플것이며 약간은 생소한 개념도 보실 거 같습니다.. 움찔 -_-;


새로운 무언가를 배울때는
샘플 코드와 그에 대한 설명을 같이 보면서 이해해 가는 것이


최고의 방법이라고 생각한답니다.


 


불행(?)하게도 이번 주는
휴가나온 친구들과 술을 많이 마셔서 -_-;;


하고 싶었던 Condition Variable에
대한 이야기를 다음으로 미루어야 할 거 같습니다 -_-ㆀ


죄송합니다 ㅜ_ㅜ;


 


대신 이 이야기는 Once Routine과
함께 써서 Boost.Threads에 대한 이야기를 마무리 지을려고 합니다.


이제 고지에 거의 다 온 듯
합니다.


 


자 그럼 떠나보도록 할까요
~


 


* Mutexes


 


멀티쓰레드 프로그램을 작성해
보셨다면.. 각각의 쓰레드들에 의해서 공유되는 자원을


관리하는 것이 얼마나 중요한
작업인지를 이해하실 겁니다. 즉 한 쓰레드가 공유된 자원의 값을 변경하는


동안 다른 쓰레드가 이 공유된
자원의 값을 읽어드린다면 이 결과는 정의되어 있지 않습니다..


헉 -_-;; 무시무시합니다.


 


이러한 현상이 발생되는 경우를
막기 위해서 사용자들은 여러 가지 동기화 객체나 오퍼레이션을 사용합니다.


예를 들면… Critical Section,
Mutex, Semaphore, Event, InterlockedIncrement().... etc -_-;;;


참 많기도 많습니다 ^^; 다
존재의 이유가 존재하겠지요 ~!


 


이러한 타입들 중 가장 기본적이면서도
가장 많이 알려진 타입 중 하나가 바로 mutex입니다.. ^^;


mutex는 공유 자원에 대해서
두 쓰레드 이상이 동시에 접근하지 못하도록 해주는 객체입니다.


즉 오로지 한 쓰레드만이
공유자원에 접근 가능합니다  !


전체적인 동작 방식은 .....
저보다 잘 아시겠지만 그래도 한번 적어보고 갈려고 합니다.


 


일단 쓰레드가 공유된 자원을
엑세스할 경우 먼저 mutex를 lock을 시킵니다.


만약 다른 쓰레드가 이미
lock이 걸린 mutex를 가지고 있다면 이 쓰레드들은 다른 쓰레드에 의해서


mutex가 unlock될 때까지
대기하게 됩니다.


따라서 오로지 동시에 한
쓰레드만이 공유된 자원을 엑세스 할 수 있는 것을 보장해 줍니다.


이야~~ 간단하게 설명했습니다
^^;


 


그러나 mutex에는 다양한
컨셉이 존재합니다.


그 중 Boost.Threads에서는
크게 아래와 같은 2가지 컨셉의 mutex를 지원합니다.


 


* Simple Mutex


* Recursive Mutex


 


simple mutex는 오직 한번만
lock이 가능합니다.


즉 동일한 쓰레드가 mutex를
두번 lock하려고 한다면 deadlock이 되며


이것은 쓰레드가 영원히 대기한다는
것을 의미합니다. 역시 무시무시 합니다 ^^;


 


반면에 recursive mutex는
한 쓰레드에 의해서 여러 번 lock되는 것이 가능합니다.


단 다른 쓰레드에 의해서
mutex가 lock되기 위해서는 lock된 횟수 만큼 unlock을 해주어야 합니다.


기억엔 windows가 지원해
주는 mutex가 이러한 방식이었던 거처럼 기억됩니다 ^^;;


하핫 잘 모르겠습니다.. 맞나요
?


 


자 이제 우리는 이 두가지
컨셉을 조합하여 다양한 방식으로 mutex를 lock할 수가 있습니다. 오홋 !


즉 쓰레드에서는 아마도 다음의
3가지 방식으로 lock을 시도할 수 있을 거 같습니다.


 


1. mutex를 lock하고 있는
쓰레드가 없을 때까지 대기하는 방식


2. mutex를 lock하고 있는
쓰레드가 존재한다면 즉시 리턴하는 방식


3. mutex를 lock하고 있는
쓰레드가 없을 때까지 혹은 명시된 시간까지 대기하는 방식


 


각각의 방식은 나름대로의
장점과 단점을 가지고 있을 뿐만 아니라


오버헤드도 다르기 때문에
Boost.Threads에서는 사용자의 필요에 따라 가장 효율적인 mutex타입을 고를 수 있도록
해두었습니다.


이러한 이유로 하여 Boost.Threads에서는
6가지의 mutex타입을 지원한답니다.


 


boost::mutex,


boost::try_mutex


boost::timed_mutex


 


boost::recursive_mutex


boost::recursive_try_mutex


boost::recursive_timed_mutex


 


자 이름을 보아하니 위의
셜명과 매치가 되십니까 ^^;


 


대부분의 초보 개발자들이
범하는 실수는 mutex를 lock하고 이것을 순차적으로 unlock하지 않기 때문에 발생하는
deadlock입니다.


이러한 실수가 비일비재하기
때문에 Boost.Threads에서는 이러한 현상이 일어나는 것을 불가능하게 디자인하였답니다.


즉 어떠한 mutex타입에 대해서도
사용자가 직접적으로 lock을 걸거나 unlock할 수 없답니다 !


 


그 대신에 boost의 mutex클래스들은
내부적으로 mutex를 locking하고 unlocking하기 위한


RAII(Resourec Acquisition
in Initialization)를 구현한 타입을 typedef하고 있습니다.


내부적으로 말이지요..


RAII...... 개념은 Scoped
Lock pattern이라고 더 잘 알려져 있습니다.


잘 알고 계신분들도 있고
생소한 분들도 있으리라고 생각됩니다.


 


그다지 어려운 개념이 아니랍니다.
^-^;; -_-;; -_-ㆀ


간단히 말해서 생성자에서
mutex를 lock하고 소멸자에서 unlock을 해준다는 개념입니다.


 


C++에서는 생성자가 올바르게
완료된 객체에 한해서 어떠한 경우에도 소멸자가 항상 불려준다는 것을


보장해 주기 때문에 예외가
발생하더라도 mutex를 unlock하는 것을 보장해 줍니다.


따라서 이 패턴을 사용하면
mutex 사용을 편리하게 도와줄 수 있습니다.


 


그러나 명심해야만 할 사실은
Scoped Lock pattern이 mutex가 unlock되는 것을 보장해 주지만


공유된 리소스가 예외가 던져지더라도
유효한 상태로 남아있다는 것은 보장하지 않는다는 점이랍니다.


이점은 꼭 명심을 하셔야
한답니다.


 


이 부분에 대한 자세한 설명은


“Pattern-Oriented Software
Architecture, Volume 2”참조하시면 찾아보실 수 있답니다. ^^;


 


자 이제 뜬구름 잡는 이야기는
그만하고 아주 간단한 샘플을 보면서 이야기를 해볼 시간이 된 거 같습니다.



    
        
    
    
        
    

            

Sample No 3 : The
Boost::mutex Class


        

            

#include


            

#include


            

#include


            

 


            

boost::mutex
            io_mutex;


            

 


            

sturct Message


            

{


            

    Message(int
            nId) : m_nId(nId) {}


            

    


            

    void
            operator() ()


            

    {


            

        for
            (int n = 0; n<10; n++)


            

        {   


            

            boost::mutex::scoped_lock
            lockObj(io_mutex);


            

            std::cout
            << ID Number : << m_nId << std::endl;


            

        }


            

    }


            

    


            

    int
            m_nId;


            

};


            

 


            

int main()


            

{


            

    boost::thread
            messageThrd1(Message(1));


            

    boost::thread
            messageThrd2(Message(2));


            

    messageThrd1.join();


            

    messageThrd2.join();


            

    return
            0;


            

}


        

 


 


자 먼저 main() 함수부터
살펴 보도록 하겠습니다.


main()에서는 두개의 쓰레드를
생성하고 있으며 이 두 쓰레드가 완료 될 때까지 대기하고 있습니다.


그죠 ?


 


자 그럼 쓰레드 안쪽에서는
어떠한 일들이 일어나고 있을까요 ?


네 ~ 쓰레드 안쪽에서는 10번의
루프를 돌면서 표준 출력 객체인 std::cout을 사용하여 값을 출력하고 있습니다.


자 주목해야 할 부분은 바로
“std::cout” 입니다.


이 객체는 쓰레드에 의해서
공유된 리소스이고 따라서 각기 다른 쓰레드에 의해서 동시에 엑세스 될 경우 결과가
어떻게


될지 아무도 알 수 없습니다.
따라서 이 부분을 우리가 처리를 해주어야 겠지요.  HOW to ?


 


네 맞습니다 ~! 맨 위에 보이듯이
전역으로 선언한 io_mutex를 사용함으로써 동시에 접근되는 것을 방지하는 것이지요~;


 


boost::mutex::scoped_lock
lockObj (io_mutex);


 


lockObj가 생성되었기 때문에
당연히 생성자가 불리게 되겠지요.


그럼 당연히 io_mutex에 lock을
걸게 되는 것이랍니다. 해제는 언제 ?


네.. lockObj가 소멸될 때
자동적으로 unlock을 하겠지요 ^^;


 


하핫 ~ ^^;


벌써부터 함수-객체를 직접
손으로 작성하기 싫어지셨습니까 ~;


그럼 두번째 이야기에서 나온
Boost::Bind를 사용하면 되겠지요



    
        
    
    
        
    

Sample No 2 : Using
            Boost::Bind  


            

#include


            

#include


            

#include


            

#include


            

 


            

void Message(int
            nId)


            

{


            

    for
            (int n = 0; n<10; n++)


            

    {   


            

        boost::mutex::scoped_lock
            lockObj(io_mutex);


            

        std::cout
            << ID Number : << m_nId << std::endl;


            

    }


            

}


            

 


            

int main()


            

{


            

    boost::thread
            messageThrd1(boost::bind(&Message, 1));


            

    boost::thread
            messageThrd1(boost::bind(&Message, 2));


            

    messageThrd1.join();


            

    messageThrd2.join();


            

    return
            0;


            

}


        

 


 


참고 문헌


:
http://boost.org/libs/thread/doc/index.html


: C / C++ Users
Journal


: Pattern-Oriented Software Architecture, Volume 2


 


작성자


: 냐옹이 정환군
(191cm@korea.com.no_spam)