본문 바로가기
Programming/C C++

스레드와 C언어 스레드 pthread

by whitele 2021. 6. 19.
반응형

스레드 개념

 스레드는 fork 함수가 하는 것처럼 자식 프로세스처럼 역할을 하지만 경량화된 형태이다. 새로운 프로세스에 비해 적은 비용으로 생성과 관리가 가능하다. 스레드는 하나의 실행 흐름을 말하며 환경에 따라 여러 스레드를 동시에 실행이 가능하다. 하나의 프로세스에서 여러 개의 스레드가 서로 자원과 메모리를 공유하여 작동한다

 

스레드 관리

 스레드를 누가 관리하느냐에 따라서 이를 나눌 수 있다. 큰 차이가 없어 보이나 그 방식과 원리에서 보면 큰 차이가 있다.

사용자 수준의 스레드

 커널 밖에서 구현된 스레드로 같은 메모리에서 스레드가 생성 및 관리되어 속도가 빠르다. 그러나 하나의 스레드가 시스템 호출이나 장애 등으로 중단이 되면 나머지 모두도 중단이 된다. 브라우저를 사용하다 하나의 탭이 멈추면 나머지가 다 멈추는 것도 이와 비슷하다.

커널 수준의 스레드

 운영체제, 커널이 직접 스레드를 관리하는 방식이다. 어떤 스레드가 멈추더라도 다른 스레드는 그대로 진행이 되며 커널이 스스로 판단하여 스레드를 관리한다. 때문에 조금 느리다는 단점이 존재한다.

 

 

스레드

 응용프로그램을 실행하면 코드코드 데이터 영역, 전역 변수데이터 영역, 지역변수스택, 동적 할당된 공간은 힙 영역의 저장이 된다. 메모리의 어느 정도 지식이 있다면 이 정도는 충분히 이해했을 것이다.

 별도의 프로세스를 이용할 경우 이 모든 것 이외 레지스터, 메모리 공간 이외의 것들을 별도로 가지게 된다. 이렇기에 높은 비용이 들뿐만 아니라 오버헤드 가능성이 존재하고 같은 자원을 써야 할 경우 공유의 어려움이 생긴다. 이러한 문제를 해결해 주는 것이 스레드이다. 통상적으로 프로세스 하나에 스레드가 한 개였다면 여러 개가 가능하며 멀티스레드라 한다.

스레드(멀티스레드)

멀티스레드

한 프로세스에 두 개의 스레드가 있는 경우이다. 스레드를 이해하는데 많은 도움을 줄 것이다. 각각의 스레드는 독립된 레지스터와 스택을 갖고 나머지 것들은 공유하게 된다.

 

스레드의 장점

  • (커널 수준의 경우) 응용프로그램 일부분이 시스템 호출 등으로 차단되는 경우에도 실행 유지가 가능하다
  • 프로세스 내 스레드 간 자원과 메모리 공유가 가능하다
  • 스레드 생성, 문맥 교환에 있어서 매우 빠르다
  • 병렬로 처리가 가능하다.

 

프로세스와 스레드 비교

공유

   프로세스 : 모든 변수에 대해 공유하지 않는다.

   스레드 : 동일 프로세스의 스레드 간 공유를 한다. 지역 변수는 공유하지 않는다.

식별자

   프로세스 : 서로 다른 식별자를 사용한다.

   스레드 : 동일한 프로세스 식별자를 사용한다.

자료교환

   프로세스 : 운영체제의 도움으로 통신을 한다.

   스레드 : 전역 변수로 통신이 가능하다.

 

 

 

C 언어 스레드 Pthread (with Linux, Unix)

 pthread를 이용하여 스레드를 구현할 것이다. POSIX에서 스레드 생성, 동기화를 위해 만든 표준 API이다. 유닉스와 리눅스 계열에서만 pthread를 지원하며 윈도우는 지원하지 않는다. 라이브러리를 링크해야 하기에 컴파일 시 -lpthread를 옵션으로 넣어야 한다.

 

pthread 함수

pthread_create()

스레드 생성 함수

pthread_create 함수 기본형

int ptread_create(pthread_t *th,pthread_attr_t *attri, void *(*new_routine)(void*),void *arg)

인자

th : 스레드의 식별자를 저장하는 변수의 포인터

attr : 스레드 속성 보통은 NULL을 사용

new_routine : 함수를 가리키는 포인터, 새로운 실행 흐름

arg : 호출되는 함수의 인자 입력 값, 없을 시 NULL

pthread_crate 사용 코드

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *pthread_ex(void *);
int main(){
	int sts;
	pthread_t thread_id;
	void *t_return;
	if((sts=pthread_create((&thread_id),NULL,pthread_ex,NULL))!=0){
		perror("error\n\n");
		exit(1);
	}
	printf("thread id %lx \n",thread_id);
	sleep(3);
	return 0;
}
void *pthread_ex(void *arg)
{
	int i=0;
	while(i<10){
		sleep(1);
		printf("thread.. %d\n",i);
		i++;
	}
}

pthread 실행 결과

 위 실행 예를 보면 main이 종료됨에 따라서 동시에 스레드도 동시에 종료된다. pthread_ex함수는 i가 10까지 증가할 때까지 루프를 돌아야 하지만 그전에 종료된다.

 

pthread_join()

 main 함수가 종료됨에 따라 나머지 스레드도 종료된다. 작업을 마무리하지 않고 끝내기 때문에 이를 막기 위해 pthread_join() 함수를 사용한다. 메인 함수 내에 thread를 지정하여 스레드가 작업을 끝낼 때까지 메인 스레드를 대기시킨다.

pthread_join함수 기본형

int pthread_join(pthread_t *thread,void **sts);

thread : 스레드

sts : 스레드 반환 값에 접근하는 포인터

pthread_join 사용 코드

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

void *thread_ex(void *arg);
int main(){
	int sts;
	pthread_t thread_id;
	void *t_return;
	void** re;
	sts=pthread_create(&thread_id,NULL,thread_ex,NULL);
	printf("thread_id : %lx\n",thread_id);
	sleep(3);
	pthread_join(thread_id,(void**)re);
	//pthread_join(sts,(void**)re);
	return 0;
}
void *thread_ex(void *arg){
	int i=0;
	while(i<10){
		printf("threading %d\n",i);
		i++;
		if(i==10)
			exit(1);
	}
}

위 실행 예에서는 main함수의 스레드가 먼저 종료되었음에도 불구하고 다른 스레드가 종료될 때까지 대기하고 종료하지 않는다.

 

 

Mutex와 필요성

 스레드를 이용할 때 전역 변수를 사용하면 서로가 공유하게 된다. 이 때문에 임계 영역 문제가 발생한다. 스레드의 실행 순서는 실행마다 다르며 이 때문에 실행할 때마다 결괏값이 달라질 수 있다. pthread는 mutex기능을 제공하여 이 문제를 해결한다.

 mutex는 키를 이용하여 자원의 소유권을 인증하는 방식으로 상호 배제를 달성한다.

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int sum;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *sum1(void* num){
	int n=*((int *)num);
	for(int i=0;i<n;i++){
		pthread_mutex_lock(&mutex);
		sum++;
		printf("sum1:%d\n",sum);
		pthread_mutex_unlock(&mutex);
		sleep(0.3);
	}
}
void *sum2(void *num){
	int n=*((int*)num);
	for(int i=0;i<n;i++){
		
		pthread_mutex_lock(&mutex);
		sum++;
		printf("sum2:%d\n",sum);
		pthread_mutex_unlock(&mutex);
		sleep(0.3);
	}
}
int main(){
	int thread;
	pthread_t p_thread[2];
	int sts;
	int arg;
	sum=0;
	arg=5;
	thread=pthread_create(&p_thread[0],NULL,sum1,(void *)&arg);
	thread=pthread_create(&p_thread[1],NULL,sum2,(void *)&arg);
	pthread_join(p_thread[0],(void**)&sts);
	pthread_join(p_thread[1],(void**)&sts);
	sts=pthread_mutex_destroy(&mutex);
	
	return 0;
	
}

pthread_mutex 실행 결과

mutex 변수를 선언 후 초기화한다. sum1, 2 함수에서 인자를 받고 for문 안에서 mutex 변수(키)를 잠그는 동안 sum값을 증가시킨다. 이후에 mutex의 잠금을 해제한다. main함수에서 모두 사용을 끝낸 mutex는 pthread_mutex_destroy 함수를 이용하여 mutex를 해제한다. 

 pthread_mutex_lock()과 pthread_mutex_unlock() 사이에 있는 공간은 스레드가 상호 배제를 위한 공간이라고 생각하면 된다.

728x90
반응형

'Programming > C C++' 카테고리의 다른 글

C/C++ memset() 함수  (0) 2021.07.11
Segmentation Fault 세그멘테이션 오류 (Core dump)  (0) 2021.07.05
C++ STL map 컨테이너  (2) 2021.06.09
C++ STL Set 컨테이너,멤버 함수 사용  (2) 2021.06.05
C++ STL List 컨테이너  (0) 2021.06.01

댓글