thread
1. 워커 스레드
: 두 개 이상의 스레드를 마치 일꾼(Worker)처럼 main 함수가 관리하는 형태를 띠기 때문에 붙여짐

#include <stdio.h>
#include <pthread.h>
void * thread_summation(void * arg);
int sum=0;
int main(int argc, char *argv[])
{
pthread_t id_t1, id_t2;
int range1[]={1, 5};
int range2[]={6, 10};
pthread_create(&id_t1, NULL, thread_summation, (void *)range1);
pthread_create(&id_t2, NULL, thread_summation, (void *)range2);
pthread_join(id_t1, NULL);
pthread_join(id_t2, NULL);
printf("result: %d \n", sum);
return 0;
}
void * thread_summation(void * arg)
{
int start=((int*)arg)[0];
int end=((int*)arg)[1];
while(start<=end)
{
sum+=start;
start++;
}
return NULL;
}
> result: 55
id_t1이 작동한 후 id_t2가 작동하여 실제 값은 정확히 출력됨
2. 임계영역
1) 두 개 이상의 스레드를 통한 문제점 발견
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100
void *thread_inc(void *arg);
void *thread_des(void *arg);
// 전역변수 num을 선언
// long long 타입으로 선언하여 큰 수를 저장할 수 있도록 함
long long num = 0;
int main(int argc, char *argv[])
{
pthread_t thread_id[NUM_THREAD];
int i;
printf("sizeof long long: %ld \n", sizeof(long long));
for (i = 0; i < NUM_THREAD; i++)
{
// 스레드 생성
// 짝수 인덱스는 thread_des 함수를 실행하고, 홀수 인덱스는 thread_inc 함수를 실행
if (i % 2)
pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
else
pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
}
// 스레드가 종료될 때까지 대기
for (i = 0; i < NUM_THREAD; i++)
pthread_join(thread_id[i], NULL);
printf("result: %lld \n", num);
return 0;
}
// 두 개의 스레드 함수 정의
// thread_inc 함수는 num을 50000000번 증가시킴
void *thread_inc(void *arg)
{
int i;
for (i = 0; i < 50000000; i++)
num += 1;
return NULL;
}
// thread_des 함수는 num을 50000000번 감소시킴
void *thread_des(void *arg)
{
int i;
for (i = 0; i < 50000000; i++)
num -= 1;
return NULL;
}
> sizeof long long: 8
전역변수 num의 저장값은 0이 되어야 할 것같다
하지만 출력값은 8이 나오게 된다
이유가 뭘까?
이유는 하나의 변수(num)에 둘 이상의 스레드가 동시에 접근했기 때문
2) 문제가 되는 임계영역
임계영역이란 둘 이상의 스레드가 동시에 실행했을 때 문제를 일으키는 코드블록이라고 함
여기서 문제가 되는 부분은 아래다.
void *thread_inc(void *arg)
{
int i;
for (i = 0; i < 50000000; i++)
num += 1;
return NULL;
}
// thread_des 함수는 num을 50000000번 감소시킴
void *thread_des(void *arg)
{
int i;
for (i = 0; i < 50000000; i++)
num -= 1;
return NULL;
}
서로 다른 코드블록에서 전역변수 num에 접근하기 때문에 문제가 발생하는 것이다.
3) 해결방법인 동기화(Synchronization)
사실은...
두 함수가 변수에 "순서대로" 접근하면 문제는 생기지 않는다
위 c 파일에서 thread_inc가 접근한 후 thread_des가 접근 이후 thread_inc 에 접근하는 이상적인 상황이 진행된다면 전역변수 num의 저장값은 우리의 생각대로 0이 나올 것이다.
하지만 CPU의 실행은 thread_inc가 변수를 증가시켜 저장하기도 전에 thread_inc로 넘어갈 수 있다는 문제가 있다
따라서
한 스레드가 하나의 변수에 접근해서 연산을 완료하기 전까지 다른 스레드의 접근을 막아야 할 필요성이 있다
이를 '동기화' 라고 한다.
3. 뮤텍스(Mutex, Mutual Exclusion) in 동기화
: 스레드의 동시접근을 차단, 마치 자물쇠와 같은 역할
1) 뮤텍스 생성과 소멸

※ pthread_mutex_t형은 pthread.h 헤더파일에 저장되어 있는 사용자 정의 자료형이다
2) 뮤텍스 차단과 비차단

3) 실제 사용

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100
void * thread_inc(void * arg);
void * thread_des(void * arg);
long long num=0;
pthread_mutex_t mutex;
int main(int argc, char *argv[])
{
pthread_t thread_id[NUM_THREAD];
int i;
// mutex 생성
pthread_mutex_init(&mutex, NULL);
for(i=0; i<NUM_THREAD; i++)
{
if(i%2)
pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
else
pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
}
for(i=0; i<NUM_THREAD; i++)
pthread_join(thread_id[i], NULL);
printf("result: %lld \n", num);
// mutex 소멸
pthread_mutex_destroy(&mutex);
return 0;
}
void * thread_inc(void * arg)
{
int i;
// 임계영역 부분을 mutex로 차단(lock)
pthread_mutex_lock(&mutex);
for(i=0; i<50000000; i++)
num+=1;
// 임계영역 부분을 mutex로 차단 해제(unlock)
pthread_mutex_unlock(&mutex);
return NULL;
}
void * thread_des(void * arg)
{
int i;
for(i=0; i<50000000; i++)
{
// 임계영역 부분을 mutex로 차단(lock)
pthread_mutex_lock(&mutex);
num-=1;
// 임계영역 부분을 mutex로 차단 해제(unlock)
pthread_mutex_unlock(&mutex);
}
return NULL;
}
> result: 0
실제 출력 시까지는 꽤나 오래걸리는데,
이유는 뮤텍스 lock, unlock 호출이 시간을 많이 소모하기 때문임
thread_inc와 thread_des에서 mutex_lock, unlock의 범위차이가 있는 것을 알 수 있다
thread_inc는 50000000번을 반복하는 for문 앞에 mutex가 있어 1번만 lock, unlock을 하는 반면
thread_des는 for문 안에 있어 5천만번을 lock, unlock을 하게 됨
이는 명확한 속도 차이를 보여줌
void * thread_des(void * arg)
{
int i;
// 임계영역 부분을 mutex로 차단(lock)
pthread_mutex_lock(&mutex);
for(i=0; i<50000000; i++)
num-=1;
// 임계영역 부분을 mutex로 차단 해제(unlock)
pthread_mutex_unlock(&mutex);
return NULL;
}
위와 같이 thread_des를 thread_inc처럼 mutex를 배치하게 되면 출력이 매우 빨라짐을 알 수 있다
4. 세마포어(Semaphore) in 동기화
1) 세마포어 생성과 소멸

2) 세마포어 호출과 대기


sem_init 함수가 호출되면 운영체제가 세마포어 오브젝트를 생성하는데,
여기에 세마포어 값(value)이라는 불리는 정수가 하나 기록되게 된다
이 값은 sem_post가 호출되면 1 증가, sem_wait이 호출되면 1 감소하게 된다
하지만 세마포어 값은 0보다 작아질 수 없는 조건을 가지고 있으므로
값이 0인 상태에서는 sem_wait을 호출해도 함수가 반환되지 않고 블로킹 상태에 놓이는 것이다
따라서 A 스레드가 세마포어 값이 1인 상태(접근 가능한 상태)에서 sem_wait로 1을 감소시켜 다른 스레드의 접근을 블로킹하고, sem_post로 1을 증가시켜야 블로킹이 해제되어 다른 스레드에서 접근 가능하게 되는 것임
이를 0과 1을 오가기 때문에 바이너리 세마포어라고 하는 것
3) 실제 사용

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
void * read(void * arg);
void * accu(void * arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;
int main(int argc, char *argv[])
{
pthread_t id_t1, id_t2;
//세마포어 값을 2개 생성
sem_init(&sem_one, 0, 0);
sem_init(&sem_two, 0, 1);
pthread_create(&id_t1, NULL, read, NULL);
pthread_create(&id_t2, NULL, accu, NULL);
pthread_join(id_t1, NULL);
pthread_join(id_t2, NULL);
sem_destroy(&sem_one);
sem_destroy(&sem_two);
return 0;
}
void * read(void * arg)
{
int i;
for(i=0; i<5; i++)
{
fputs("Input num: ", stdout);
// 세마포어 sem_two를 wait하면 1이 감소
sem_wait(&sem_two);
scanf("%d", &num);
// 세마포어 sem_one을 post하면 1이 증가
sem_post(&sem_one);
}
return NULL;
}
void * accu(void * arg)
{
int sum=0, i;
for(i=0; i<5; i++)
{
// 세마포어 sem_one을 wait하면 1이 감소
sem_wait(&sem_one);
sum+=num;
// 세마포어 sem_two를 post하면 1이 증가
sem_post(&sem_two);
}
printf("Result: %d \n", sum);
return NULL;
}
>
Input num: 5
Input num: 6
Input num: 3
Input num: 2
Input num: 1
Result: 17
여기서 세마포어 값을 2개 생성하는데 one은 0으로 two는 1로 초기화 한다
이유는 순서를 조정하기 위함인데
위에서 세마포어 값이 0인 상태에서는 블로킹 상태에 걸린다는 것을 알 수 있었다
이 경우 accu 함수에서는 one의 세마포어 값이 0이므로 먼저 접근이 불가능한 반면
read 함수에서는 two의 세마포어 값이 1이므로 먼저 접근이 가능하다
먼저 read하고, 그 값을 accu(연산) 해야하기 때문에 순서가 매우 중요한 것 !
1) read 함수가 먼저
접근 가능한 two를 wait하면서 블로킹 상태를 만들며 먼저 scanf를 수행하고
블로킹 상태였던 one을 post함으로써 블로킹 상태를 해제한다
2) accu 함수가 그 다음으로
접근 가능한 one을 wait하면서 블로킹 상태를 만들며 sum += num 을 수행하고
블로킹 상태인 two를 post함으로써 블로킹 상태를 해제한다
5. 소멸
스레드 main 함수를 반환해도 자동으로 소멸되지 않으므로
detach 함수를 통해 직접 스레드를 소멸시켜야 함

'LMS 7 > 개발일지' 카테고리의 다른 글
| 25.07.12 학습일지 / mySQL, C (0) | 2025.07.29 |
|---|---|
| 25.07.10 학습개발일지 / mySQL (0) | 2025.07.28 |
| 25.07.03 학습일지 (0) | 2025.07.28 |
| 25.07.01 학습개발일지 / TCP IP SOCKET (0) | 2025.07.28 |
| 25.06.23 개발일지 (3) | 2025.07.24 |