• 프로세스란 : 코드실행을 한 순간에 한 부분만 실행하는 것
  • multi-thread : 하나의 프로세스의 code를 수행하는 부분들이 여러개 -> 동시에 처리하므로 처리시간이 빨라짐
  • -> CPU들이 여러개(core)가 있는 경우에 process는 core하나만 사용하고 나머지 core는 사용하지 못하지만 쓰레드를 이용하면 core들이 각 쓰레드를 동시에 처리
  • Pthread가 default로 사용됨
  • thread를 만든 후 main쓰레드는 그대로 동작, 만들어진 쓰레드는 지정된 함수를 수행
  • thread들이 다른 쓰레드가 끝나기를 기다리는 경우 pthread_join을 사용
  • 예시 : 두 쓰레드가 동시에 같은 함수를 수행하는 경우 -> for문으로 50000번씩 i를 증가시키며 출력하는 경우
    • 원하는 결과값이 나오지 않고 조금 작은 값이 나옴
    • critical section이 동시에 수행되면 안됨
    • 한 쓰레드가 수행중에 다른 쓰레드가 대기하도록 하는 mutex 사용하여 해결

pthread_crate

  • 쓰레드로 실행하는 코드 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
 
void *worker(void *arg){
        printf("hello worker\n");
        pthread_exit(NULL);
}
 
int main(void)
{
        pthread_t mythread;
        int result;
 
        result = pthread_create(&mythread,NULL,worker,NULL);
 
        if(result){
                exit(1);
        }
 
        printf("main thread exit\n");
        pthread_exit(NULL);
}
cs
  1. worker라는 함수를 선언 -> hello worker를 출력
  2. pthread_t 타입으로 thread로 지정할 변수를 선언
  3. pthread_create로 mythread(worker)를 실행
  4. result값이 1이면 프로그램 종료
  5. 메인문에서는 main thread exit라는 문장을 출력
  6. 결과

  • 결과값이 실행 되지만 메인문의 출력이 먼저 실행됨

pthread_join

  • 메인문을 수행하기 전에 지정한 쓰레드를 수행할 수 있도록 join으로 기다리기
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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
 
void *worker(void *arg){
        printf("hello worker\n");
        pthread_exit(NULL);
}
 
int main(void)
{
        pthread_t mythread;
        int result;
 
        result = pthread_create(&mythread,NULL,worker,NULL);
 
        if(result){
                perror("pthread_create");
                exit(0);
        }
 
        pthread_join(mythread,NULL);
        printf("main thread exit\n");
        pthread_exit(NULL);
}
cs
  1. pthread_join을 메인문 출력 전에 사용하여 한 쓰레드가 끝날때 까지 기다리도록 함
  2. 결과

  • 두 출력의 위치가 달라짐

쓰레드간의 데이터 공유

  • 전역변수 두개를 선언 - counter, running -> 모든 쓰레드에서 접근 가능한 변수
  • worker함수는 counter값 ++ -> running이 0이 되면 중단
  • main문에서 counter값을 1초에 한번씩 총 10번 출력 후 나와서 running을 0으로 바꿔줌
  • worker쓰레드 종료
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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
 
volatile long long counter = 0;
volatile int running = 1;
 
void *worker(void *arg){
        while(running){
                counter++;
        }
        pthread_exit(NULL);
}
 
int main(void)
{
        pthread_t mythread;
        int result;
 
        result = pthread_create(&mythread,NULL,worker,NULL);
 
        if(result){
                perror("pthread_create");
                exit(0);
        }
 
        for(int i = 0; i<10; i++){
                printf("%lld\n",counter);
                sleep(1);
        }
 
        running = 0;
        pthread_join(mythread,NULL);
        printf("main thread exit\n");
        pthread_exit(NULL);
}
cs
  1. counter와 running을 선언 -> counter는 while문에서 늘어날 값, running은 flag로 작용할 값
  2. 위와 동일하게 쓰레드를 생성하고 worker에는 running이 0이되면 counter증가를 멈추는 함수를 넣음
  3. 메인문에서는 1초에 한번씩 counter를 총 10번 출력하는 for문을 작성
  4. 10번을 모두 출력하면 running을 0으로 바꿔줌
  5. 결과

  • 점점 값이 늘어나다가 10번 출력한 후 종료됨
  • 멀티프로세서(코어) 시스템에서의 유의점
  • gcc myprog.c -lpthread

  • gcc -O2 myprog.c -lpthread

  • 이유 : cpu들에는 각각 cach가 존재하는데 cach와 메인메모리 사이에 데이터를 주고 받을 때 많은 시간이 걸리므로 optimization을 하는 경우 counter값과 running값을 캐시로 불러와서 수행하기 때문에 각 쓰레드간 데이터 공유가 되지 않음 -> cach로 가져오지 않고 메모리에서만 사용하도록 지정하는 volatile으로 선언해야 -O 명령을 쓸 수 있음

 

 


쓰레드에 인자 전달

  • 쓰레드 4개를 만듦
  • 각각의 쓰레드에게 파라미터 값을 전달
  • start부터 end까지 더함
  • myid에 전부 더한값을 넣음
  • 1~1000, 1001~2000, 2001~3000, 3001~4000
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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
 
#define NUM_THREADS 4
#define WORK_PER_THREAD 1000
 
struct thread_args{
    int myld;
        long long start;
        long long end;
};
 
volatile long long mysum[NUM_THREADS];
 
void *worker(void *arg){
    struct thread_args *ta;
 
    ta = (struct thread_args *)arg;
    long long sum = 0;
 
    for(int j=ta->start; j <= ta->end; j++){
        sum = sum + j;
    }
    mysum[ta->myld] = sum;
 
    pthread_exit(NULL);
}
 
int main(void)
{
    pthread_t mythread[NUM_THREADS];
    struct thread_args range[NUM_THREADS];
    int i;
 
    for(i=0; i<NUM_THREADS; i++){
        range[i].myld = i;
        range[i].start = i*WORK_PER_THREAD+1;
        range[i].end = (i+1)*WORK_PER_THREAD;
        pthread_create(&mythread[i],NULL,worker,&range[i]);
    }
 
    for(i=0; i<NUM_THREADS; i++){
        pthread_join(mythread[i],NULL);
        printf("mysum[%d]: %lld\n",i,mysum[i]);
    }
    printf("main thread exit\n");
    pthread_exit(NULL);
}
cs
  1. 쓰레드를 4개 선언
  2. 구조체(myld - 위치를 저장, start - 덧셈을 시작할 숫자를 저장, end - 덧셈을 끝낼 숫자를 저장)역시 4개 선언
  3. 1~1000, 1001~2000, 2001~3000, 3001~4000을 각 myld = 0, 1, 2, 3에 저장
  4. 만들면서 동시에 쓰레드 생성 -> 4개의 쓰레드가 동시에 실행됨(시작 숫자 부터 끝 숫자까지 모두 더함)
  5. pthread_join으로 0번 ~ 3번 쓰레드가 끝나는 것을 순서대로 기다림
  6. 여기서는 sum이라는 변수도 4개를 선언하여 사용했으므로 레지스터값이 겹치지 않고 잘 실행됨
  7. 결과

  • 만약 sum이라는 변수를 4개 사용하지 않고 그냥 sum 하나로 모든 쓰레드에서 사용한다면?
  • 실행할 때마다 다른 값이 나올 것이다
  • 고친부분 sum[0~4] -> sum으로 통일
  • 마지막 결과 출력도 가장 마지막 값만 출력

 


쓰레드간 동기화

  • 위에서 사용한 mysum배열이 아닌 그냥 mysum이라는 변수 하나에만 더하는 경우
  • 모든 쓰레드가 동시에 한 변수를 사용하기 때문에 race condition이 발생
  • mutex를 사용해보자
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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
 
#define NUM_THREADS 4
#define WORK_PER_THREAD 1000
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
struct thread_args{
    int myld;
        long long start;
        long long end;
};
 
volatile long long mysum;
 
void *worker(void *arg){
    struct thread_args *ta;
 
    ta = (struct thread_args *)arg;
    long long sum = 0;
 
    for(int j=ta->start; j <= ta->end; j++){
        pthread_mutex_lock(&mutex);
        mysum = mysum + j;
        pthread_mutex_unlock(&mutex);
    }
 
    pthread_exit(NULL);
}
 
int main(void)
{
    pthread_t mythread[NUM_THREADS];
    struct thread_args range[NUM_THREADS];
    int i;
 
    for(i=0; i<NUM_THREADS; i++){
        range[i].myld = i;
        range[i].start = i*WORK_PER_THREAD+1;
        range[i].end = (i+1)*WORK_PER_THREAD;
        pthread_create(&mythread[i],NULL,worker,&range[i]);
    }
    for(i=0; i<NUM_THREADS; i++)
        pthread_join(mythread[i],NULL);
 
    printf("mysum: %lld\n",mysum);
    printf("main thread exit\n");
    pthread_exit(NULL);
}
cs

 

  1. mutex를 선언하여 pthread_mutex_lock과 unlock사용
  2. lock과 unlock사이의 코드는 쓰레드들이 동시에 수행하지 않고 하나의 쓰레드가 끝날때까지 대기함
  3. 결과


프로그램 수행시간 확인

  • mutex를 사용한 경우

 

  • mutex를 사용하지 않은 경우

 

  • 바른 결과를 얻기 위해 mutex를 사용하면서도 성능 감소를 막기 위해서 어떻게 해야 할까?

 

    • 위의 쓰레드의 인자전달 실습에서 사용한 것처럼 4개의 sum변수를 사용하여 각자 따로 worker함수를 수행시킨 후 마지막에 각각을 더해주는 방식으로 수행하고자 한다

 

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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
 
#define NUM_THREADS 4
#define WORK_PER_THREAD 1000
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
struct thread_args{
    int myld;
        long long start;
        long long end;
};
 
volatile long long mysum[NUM_THREADS];
volatile long long result = 0;
 
void *worker(void *arg){
    struct thread_args *ta;
 
    ta = (struct thread_args *)arg;
    long long sum = 0;
 
    for(int j=ta->start; j <= ta->end; j++){
        sum = sum + j;
    }
    mysum[ta->myld] = sum;
 
//    pthread_mutex_lock(&mutex);
    result = result + mysum[ta->myld];
//    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}
 
int main(void)
{
    pthread_t mythread[NUM_THREADS];
    struct thread_args range[NUM_THREADS];
    int i;
 
    for(i=0; i<NUM_THREADS; i++){
        range[i].myld = i;
        range[i].start = i*WORK_PER_THREAD+1;
        range[i].end = (i+1)*WORK_PER_THREAD;
        pthread_create(&mythread[i],NULL,worker,&range[i]);
    }
 
    for(i=0; i<NUM_THREADS; i++){
        pthread_join(mythread[i],NULL);
        printf("mysum[%d]: %lld\n",i,mysum[i]);
    }
    printf("main thread exit\n");
    printf("final sum = %lld\n",result);
    pthread_exit(NULL);
}
cs
  • 수정된 부분
    1. volatile long long mysum; -> volatile long long mysum[NUM_THREADS];
    2. volatile long long result = 0; 추가 -> 마지막에 모든 합들을 더하기 위한 변수
    3. //    pthread_mutex_lock(&mutex);
          result = result + mysum[ta->myld]; -> 주석처리된 부분을 사용하면 완전한 결과값만 나오고 사용하지 않으면 가끔 다른 결과값이 나온다
      //    pthread_mutex_unlock(&mutex);

       

    4. printf("final sum = %lld\n",result); -> 결과값 출력

    5. 결과비교

  • Mutex를 사용하지 않았을 때

  • Mutex를 사용했을 때
  • Mutex수행 때문에 조금 느려졌었던 이전과는 다르게 Mutex를 사용하지 않았을 때와 사용했을 때의 수행 시간이 차이가 나지 않는 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts