25.07.08 학습개발일지 / thread

2025. 7. 28. 19:12·LMS 7/개발일지

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
'LMS 7/개발일지' 카테고리의 다른 글
  • 25.07.12 학습일지 / mySQL, C
  • 25.07.10 학습개발일지 / mySQL
  • 25.07.03 학습일지
  • 25.07.01 학습개발일지 / TCP IP SOCKET
m_Dev
m_Dev
  • m_Dev
    m_Dev
    m_Dev
  • 전체
    오늘
    어제
    • 분류 전체보기
      • MAIN STUDY
        • 정보보안
        • 빅데이터
        • 정보처리
        • 컴퓨터 구조
        • 기타
      • JOB
        • Study
        • Project
      • LMS 7
        • 개발일지
      • FRAMEWORK
        • Qt
        • MFC
        • Winform
        • WPF
        • MAUI
      • NETWORK
        • Study
        • Assignment
      • PYTHON
        • Set
        • Study
        • Assignment
        • Project
      • C
        • Set
        • Study
        • Assignment
        • Project
      • C++
        • Set
        • Study
        • Assignment
        • Project
      • C#
        • Set
        • Study
        • Assignment
        • Project
      • DATABASE
        • MySQL
        • SQLite
      • IDE
        • VisualStudioCode
        • VisualStudio
        • Pycharm
        • Colab
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
m_Dev
25.07.08 학습개발일지 / thread
상단으로

티스토리툴바