"스레드 동기화가 필요한 대부분의 프로그램은 이런 간단한 시나리오만을 정확히 해결하면 되는 경우가 많으며,  복잡한 문제를

 내포하는 경우는 그다지 많지 않다. " 


그렇기 때문에 멀티스레드 소프트웨어를 작성할때는 디자인 패턴에 해당되는지 한번 살펴본후,  여기에서 제시하는 


해결책을 사용한다면 좋은 프로그램을 작성할 수 있다.



자원의 상호 배재


상호배제는 ( mutual exclusion ) 는 스레드 동기화의 가장 기초적인 디자인 패턴이다.

데이터나 코드와 같은 시스템 자원이 동시에 둘 이상의 스레드에 의해서 안전한 방법으로 접근될수 없을때 상호 배제가 사용된다.


한번에 하나의 스레드 만이 자원에 접근하도록 허용하고 싶은 경우에는 이 자원을 동기화 객체를 사용해서 보호해야 한다.


  • 직렬화 (serializing ) :  첫번째 스레드가 자원을 사용하는 동안 두번째 스레드가 접근하려고 하면 둘째 스레드는 블록되고
    수행이 보류된다.  이러한 방법으로 자원에 접근하는것이 serializing 이라고 한다.
  • win32 환경에서는 직렬화 하기 위해서 두개의 시스템 객체 임계영역과 뮤텍스를 사용한다. 
  • 임계영역은 부하가 적으며,  한 프로세스 안의 스레드 사이에서 사용될 때 유용하다. 
  • 뮤텍스는 다수의 프로세스에서 동작하는 스레드가 사용하는 자원에 사용되며 일반적으로 실행시간의 비용이 많이 든다.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct{
    int nTotalThreads;
    int nRedThreads;
    int nWhiteThreads;
    int nBlueThreads;
}MyThreadInfo;
 
MyThreadInfo g_myThreadInfo;
 
 
void InitializedMyThreadInfo(void);
 
void AddRedThread(void);
void AddWhiteThread(void);
void AddBlueThread(void);
 
void RemoveRedThread(void);
void RemoveWhiteThread(void);
void RemoveBlueThread(void);
 
int GetRedThreadCount(int *pnTotalThreads);
int GetWhiteThreadCount(int *pnTotalThreads);
int GetBlueThreadCount(int *pnTotalThreads);
cs


  • 다수의 스레드가 이런 함수들을 동시에 호출할 수 있기 때문에 하나의 스레드가 AddRedThread 를 호출하는 동안 다른 스레드가 GetReadThreadCount 를 호출하는 경우도 발생할 수 있다.
  • 또한 AddThreadCount 를 실행하는 스레드가 MyThreadInfo 의 hRedThreads 멤버를 증가시켯을지라도 GetRedThreadCount 를 호출한 스레드가 데이터를 읽을때 nTotalThreads 를 미처 증가 시키지 못했을수도 있다. 
  • 이런 경우가 발생하면 red 스레드의 개수와 모든 스레드의 개수가 일치하지 않을것이가. 
  • 결국 요구되는 것은 red 스레드의 개수와 모든 스레드의 개수를 동시에 읽는것이다
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    CMlcCriticalSection g_CriticalSecThreadInfo;
     
    int main(int argc, char* argv[])
    {
        return 0;
    }
     
     
    void InitializedMyThreadInfo(void)
    {
        g_myThreadInfo.nTotalThreads = 0;
        g_myThreadInfo.nRedThreads = 0;
        g_myThreadInfo.nWhiteThreads = 0;
        g_myThreadInfo.nBlueThreads = 0;
    }
     
    void AddRedThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads++;
        g_myThreadInfo.nRedThreads++;
        g_CriticalSecThreadInfo.Leave();
    }
    void AddWhiteThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads++;
        g_myThreadInfo.nWhiteThreads++;
        g_CriticalSecThreadInfo.Leave();
     
    }
    void AddBlueThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads++;
        g_myThreadInfo.nBlueThreads++;
        g_CriticalSecThreadInfo.Leave();
     
    }
     
    void RemoveRedThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads--;
        g_myThreadInfo.nRedThreads--;
        g_CriticalSecThreadInfo.Leave();
     
    }
    void RemoveWhiteThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads--;
        g_myThreadInfo.nWhiteThreads--;
        g_CriticalSecThreadInfo.Leave();
     
    }
    void RemoveBlueThread(void)
    {
        g_CriticalSecThreadInfo.Enter();
        g_myThreadInfo.nTotalThreads--;
        g_myThreadInfo.nRedThreads--;
        g_CriticalSecThreadInfo.Leave();
     
    }
     
    int GetRedThreadCount(int *pnTotalThreads)
    {
        g_CriticalSecThreadInfo.Enter();
        int nReadThread = g_myThreadInfo.nRedThreads;
        *pnTotalThreads = g_myThreadInfo.nTotalThreads;
        g_CriticalSecThreadInfo.Leave();
        return nReadThread;
    }
    int GetWhiteThreadCount(int *pnTotalThreads)
    {
        g_CriticalSecThreadInfo.Enter();
        int nReadThread = g_myThreadInfo.nRedThreads;
        *pnTotalThreads = g_myThreadInfo.nTotalThreads;
        g_CriticalSecThreadInfo.Leave();
        return nReadThread;
    }
    int GetBlueThreadCount(int *pnTotalThreads)
    {
        g_CriticalSecThreadInfo.Enter();
        int nReadThread = g_myThreadInfo.nRedThreads;
        *pnTotalThreads = g_myThreadInfo.nTotalThreads;
        g_CriticalSecThreadInfo.Leave();
        return nReadThread;
     
        return 0;
    }
     
    cs

  • 이러한 요구사항은 MyThreadInfo 구조체에 대한 접근을 직렬화 해서 쉽게 해결할 수 있다

+ 잘설계된 C++ 프로그램에서는 이런 문제를 해결하기 위해서 데이터 구조와 임계 영역을 하나로 묶고,  이 임계 영역을 사용하는 루틴을 하나의 클래스로 만들며,  애플리케이션 안에

서 전역적으로 사용할 수 있는 이 클래스의 인스턴스를 생성한다.

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    class CMyThreadInfo
    {
        int m_nTotalThreads;
        int m_nRedThread;
        int m_nWhiteThread;
        int m_nBlueThread;
     
        CMlcCriticalSection m_CricSec;
     
    public:
        CMyThreadInfo() {
            m_nTotalThreads = 0;
            m_nRedThread = 0;
            m_nWhiteThread = 0;
            m_nBlueThread = 0;
     
        };
        void AddRedThread()
        {
            m_CricSec.Enter();
            m_CricSec.m_nTotalThreads++;
            m_CricSec.m_nRedThread++;
            m_CricSec.Leave();
        };
        void AddWhiteThread()
        {
            m_CricSec.Enter();
            m_CricSec.m_nTotalThreads++;
            m_CricSec.m_nWhiteThread++;
            m_CricSec.Leave();
        };
        void AddBlueThread()
        {
            m_CricSec.Enter();
            m_CricSec.m_nTotalThreads++;
            m_CricSec.m_nBlueThread++;
            m_CricSec.Leave();
        };
     
     
     
    };
    cs



  • CMyThreadInfo 클래스는 자신만의 동기화를 제공한다. 이 클래스의 사용자는 동기회 되어 사용되는 데이터에 접근하는 것에 대해서는 알 필요가 없다.
  • 이런 동기화를 내부 동기화(Internal synchronization ) 이라 한다.  
  • 내부 동기화는 자원의 모든 사용자들이 정해진 방법을 통해서 자원을 사용할 때 유용하다.
  • 비슷한 기법으로  외부 동기화가 있다. 
    • 이 기법은 자원을 사용하는 소프트웨어 사이에서 다수의 객체가 동기화 될때 사용한다. 
  • 상호배제의 다른 사용 예는 동시에 하나 이상의 스레드에 의해서 특정 코드 영역이 사용되는 것을 막기 위한 경우이다.
  • 이런 기법은 프로세스에서도 사용될수  있으므로 매우 유용하다.
  • 가장 일반적이고 가장 간단한 형태로 상호 배제를 사용하는 방법은 한번에 하나의 애플리케이션 의 인스턴스만 동작하독 하는것이다.
    • 상다히 다양한 경우에 이런 방법이 필요하다. 
    • 프로그램이 동작하는 동안 특정 자원에 대해서 배타적으로 접근 하기를 원하거나 시스템 부하를 제한하고 싶을때  또는    프로그래므이 다수의 인스턴스가 동시에 동작하면 안되는 경우가 이에 해당한다.
    • 애플리케이션이 하나만 동작하도록 하는것은 뮤텍스를 사용하면 간단히 구현할 수 있다. 


Posted by 뉴암스테르담
l