반응형

- 본 내용은 Linux (Ubuntu 14.04 lts)를 기반으로 제작되었습니다. -




목록

1. Synchronizing (동기화)

2. Mutex

3. Semaphore

4. Race Condition

5. Deadlock






1. Synchronizing (동기화)


동기화란 무엇인가?


동기화라는 것은 Concurrent Programing에 필수 불가결한 요소이다.


이전 게시물에서도 확인하면 알다시피, Unsafe Region이 존재한다면 아무리 스레드를 만든다 한들


코더가 원하는대로 프로그램이 돌아가질 않는다.


따라서 동기화를 통해 코더가 원하는 방식대로 스레드와 메인 스레드의 조화를 이루어 낸다.

 

즉, 의도치 않은 부분에서 컨택스트 스위칭이 일어나도 프로그램이 정상작동하도록 만드는 것이 동기화다.




동기화의 핵심 요소


동기화를 이루기 위해서는 Wait와 Signaling이라는 개념을 이용해야한다.


Wait 

1. 동기화된 지역이 동작하기 전에 이 구간을 들어가도 되는지 확인한다.

2. 조건이 충족되어 들어가도 된다면 Unsafe Region에 존재하는 변수 따위를 이용하러 들어간다.

3. 조건이 충족되지 않는다면 계속해서 기다린다.(조건이 충족 될 때 까지 Unsafe Region에 들어갈 수 없다.)


Signaling

1. 동기화된 지역에서 모든 일을 마치고 Wait하고 있는 스레드를 깨워주어 들어갈 수 있도록 한다.


이러한 핵심 요소를 쓰는 동기화 방식은 크게 Mutex, Semaphore 두가지가 존재한다.






2. Mutex



뮤텍스는 Unsafe Region을 동기화된 지역으로 만들기 위해 Critical Section을 설정한다.

하나의 스레드만이 Critical Section에서 행동할 수 있다.


뮤텍스의 예시


뮤텍스는 1인 화장실을 들어가기 위한 열쇠와 같다.


무슨 의미냐면 화장실에 들어가기위해 열쇠를 가진 사람만이 화장실에 들어갈 수 있다.


화장실에 들어가있다면 다른 사람들은 그 사람이 열쇠를 주기를 기다려야한다.(이것을 대기 큐 라고한다.)


볼일이 끝나면 다음 사람이 열쇠를 받게 되는 방식이다.




뮤텍스 선언 및 이용 방법


- PTHREAD_MUTEX_INITIALIZER 매크로를 이용하여 정적 뮤텍스를 제작한다.

- 혹은 pthread_mutex_init() 함수를 이용해서 동적 뮤텍스를 제작한다.


- pthread_mutex_lock(&뮤텍스명) 을 이용하여 락을 건다

- pthread_mutex_unlock(&뮤텍스명) 을 이용하여 락을 풀어준다.




뮤텍스 파괴 방법


pthread_mutex_destroy() 함수를 이용하면 이미 선언 되어있던 뮤텍스를 파괴할 수 있다.


return 값 :: 뮤텍스가 잠겨있다면, EBUSY를 리턴한다.


** 그러므로, 뮤텍스를 파괴하기 전에는 unlock을 항상 하고 파괴해야 한다. **





뮤텍스를 이용한 예제 코드


Unsafe Region으로 인해 두 스레드를 이용하여 sum을 구하는 과정에서 2000000이 정확히 나오지 않았지만,


뮤텍스를 이용하여 2000000을 정확히 구하는 코드를 예제로 두었다.


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
// Using Mutex
 
#include <pthread.h>
#include <stdio.h>
 
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
 
int sum = 0;
 
void *threadRoutine(void *argumentPointer)
{
    int i;
 
    // 뮤텍스 락을 거는 과정
    pthread_mutex_lock(&counter_mutex);
 
    // 이 부분이 Critical Section에 해당한다.
    for(i = 0; i < 1000000; i ++)
        sum++;
 
    // 뮤텍스 락을 푸는 과정
    pthread_mutex_unlock(&counter_mutex);
    
    return NULL;
}
 
int main()
{
    pthread_t threadID1, threadID2;
 
    // Create 
    pthread_create(&threadID1, NULL, threadRoutine, NULL);
    pthread_create(&threadID2, NULL, threadRoutine, NULL);
  
    // Join 
    pthread_join(threadID1, NULL);
    pthread_join(threadID2, NULL);
 
    printf("뮤텍스를 이용한 결과 합 :: %d\n",sum);
 
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus








3. Semaphore



세마포어 또한 동기화된 지역을 만들기 위해 한정된 스레드를 Critical Section에 출입 시킨다.


세마포어는 P와 V라는 개념을 이용하는데 P는 wait, V는 post이다.


P :: wait로써 열쇠를 하나 가지고 가는 방식을 의미한다. 열쇠가 없다면 기다리고 있어야 한다.


V :: post로써 열쇠를 반납하는 방식을 의미한다.




세마포어의 예시


세마포어는 지하철 화장실처럼 화장실 칸의 개수가 여러개 유한하게 있는 곳의 열쇠의 갯수이다.


뮤텍스는 1개의 화장실에 대해서 다룬다면, 세마포어는 여러개의 화장실로 다루게 되고


화장실을 쓰기위해 열쇠를 하나 가져가면 P, 열쇠를 반납하면 V라고 한다.


만약 열쇠를 계속 가져가서(P) 열쇠가 0개가 되면, 다음 사람은 줄을 서서 기다려야한다.


화장실을 다 쓴다음 열쇠를 반납하게되면(V) 다음 사람이 들어갈 수 있다.




세마포어 선언



#include <semaphore.h>를 통해 인클루드 해준다.


sem_t semaphore; 


세마포어 생성을 의미한다.


sem_init(sem_t *sem, int pshared, unsigned int value);


첫번째 인자 :: 세마포어 객체를 초기화 할 세마포어를 받는다. (위의 sem_t semaphore가 여기 해당된다.)

두번째 인자 :: 여기에 0을 주지 않을경우 sem_init는 항상 ENOSYS 에러코드를 반환한다.(0을 쓰도록 한다.)

세번째 인자 :: 세마포어를 몇으로 초기화 할지 의미한다.(화장실 열쇠를 몇개로 시작할지 의미)


sem_wait(sem_t *sem);


세마포어의 P역할을 한다.

즉, 세마포어를 하나 감소시키는 역할을 하고 세마포어가 0일 경우에는 1이상이 될 때까지 스레드는 대기 상태에 있는다.

0이 아닐 경우에는 대기상태에서 빠져나와 세마포어를 또 1 감소시킨다.


sem_post(sem_t *sem);


세마포어의 V역할을 한다.

세마포어 값을 1 증가 시킨다.


sem_destroy(sem_t *sem);


세마포어와 관련된 리소스들을 소멸시킨다. (객체 소멸)




세마포어 이용 예제 코드


뮤텍스에서 보인 sum을 semaphore로 구하는 예제 코드이다.


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
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
 
sem_t semaphore;
 
int n;
 
void *threadFunc(void* arg)
{
    // P 기능 수행
    sem_wait(&semaphore);
 
    // Critical Section
    int i;
    for (i = 0; i < 1000000; i++)
        n++;
 
    // V 기능 수행
    sem_post(&semaphore);
 
    return 0;
}
 
int main()
{
    // 스레드 선언
    pthread_t thread1, thread2;
    
    int ret;
 
    // 세마포어 초기화(열쇠는 1개)
    ret = sem_init(&semaphore, 01); 
 
    // 세마포어 초기화 성공 여부 확인
    if (ret != 0)
    {
        printf("Semapohre init error");
        return 0;
    }
 
    // 스레드 생성
    pthread_create(&thread1, NULL, threadFunc, NULL);
    pthread_create(&thread2, NULL, threadFunc, NULL);
 
    // 스레드 조인
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
 
    // 세마포어 파괴
    sem_destroy(&semaphore);
 
    printf("합 :: %d\n",n);
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus







4. Race Condition


레이스 컨디션이란, 스케줄러가 누구에게 먼저 CPU 점유권을 줄지 모르기에 일어나는 상태이다.


레이스 컨디션은 예제 코드를 통해 확인하는 방법이 더 좋다.


Race Condition 예제


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
#include <stdio.h>
#include <pthread.h>
 
void *threadFunc(void* arg)
{
    printf("내가 받은 argument :: %d\n"*((int *)arg));
 
    return 0;
}
 
int main()
{
    // 스레드 선언
    pthread_t thread[100];
    int i;
 
    // 스레드 생성
    for (i = 0; i < 100; i++)
        pthread_create(&thread[i], NULL, threadFunc, &i);
    
 
    // 스레드 조인
    for (i = 0; i < 100; i++)
        pthread_join(thread[i], NULL);
 
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus



우리가 생각 했던 방식은,


스레드가 create로 생성될 때 i의 값이 인자로 보내져서 그 스레드에서 i에 대한 값을 출력해야 한다 생각한다.


하지만, 결과 화면은 예상과는 다르게 나타나게 된다.


그 이유는 i를 인자로 보낼 때


for문에서 한번 돌고 스레드가 한번 실행되면 그렇게 되지만,


현재 여기선 메인 스레드의 for문이 10번돌고 그다음 자식 스레드 2개가 한번씩 돌게되는 경우에


내가 받은 argument :: 10

내가 받은 argument :: 10

이렇게 출력된다.


추가적으로 메인 스레드가 3번 더 돌고 그다음 자식 스레드가 3개가 돌때


내가 받은 argument :: 13

내가 받은 argument :: 13
내가 받은 argument :: 13



이런식으로 나타난다.


아래 내가 받은 argument에서 갑자기 숫자가 늘어나다가 또 10이 나타나는 경우가 있다.


이 경우는 printf를 하는 도중에 컨택스트 스위칭을 당하여 출력을 하는 도중에 스왑이 되고,


다시 컨택스트 스위칭이되어 출력을 하는 모습이 담겨있는 것을 확인 할 수 있다. 


결국 이러한 Race Condition을 없애기 위해서는 동기화가 필요하고, Mutex, Semaphore를 통해 해결 할 수 있다.





4. Deadlock


다음 링크에 매우 자세히 설명을 해 두었기에 데드락 설명은 제외한다. http://www.crocus.co.kr/524




반응형