새소식

인기 검색어

컴퓨터공학/운영체제

4장 스레드와 멀티태스킹

  • -

Q1 : 올해 넥슨의 채용계약형 인턴 선발에서 나온 질문이다. '프로세스와 스레드의 차이점이 무엇인지 설명해보라' 이 질문에 대해 3장과 4장을 공부한 지금 차이점을 10줄로 설명하라. 다양한 관점에서 비교하고 진지하게 답을 써보라. 스레드가 왜 프로세스보다 나은 실행단위인지에 대해 잘 드러나도록 설명하라.

 

A1 : 공간적인 관점에서 프로세스들은 서로 완전히 독립적인 주소 공간을 가진다. 예를 들어 프로세스A에서 프로세스B의 주소공간에 직접접근을 할 방법이 없다. 따라서 프로세스간 통신을 하기 위해선 커널을 통해 데이터를 주고 받아야 하기 때문에 매번 시스템호출이 필요하다. 하지만 매번 시스템호출을 통한 통신을 하게되면 필요한 비용이 크기 때문에 자주 사용하면 문제점이 많다. 반면 스레드는 같은 프로세스에 속해있다면 프로세스내의 공유공간에서 커널의 개입없이 데이터를 주고받을 수 있기 때문에 시스템호출을 할 필요가 없어 데이터를 주고받는데 필요한 비용이 훨씬적다.

 

생성시간에도 큰 차이가 있는데 프로세스 생성시 독립적인 메모리공간이 할당되고 부모프로세스를 복사하며, PCB를 생성하고, 매핑테이블을 두어 주소변환을 하는 등 생성시 많은 비용과 시간이 필요하다. 하지만 스레드 생성시 TCB를 생성하고 운영체제에게 스레드를 만들어줄 것을 요청하지만 메모리공간이나 PCB, 매핑테이블은 이미 프로세스가 생성될 때 초기화 되었기 때문에 새로 만들필요가 없다.

 

컨텍스트 스위칭에서도 비용차이가 크다. 같은 프로세스내의 스레드끼리 컨텍스트 스위칭이 발생하면 매핑테이블을 교체할 필요가없지만 프로세스가 교체될때에는 매핑테이블을 교체가 필요해 프로세스가 실행단위라면 컨텍스트스위칭이 발생할때마다 오버헤드가 스레드일때보다 엄청나게 증가할것이다.

 

 

Q2 : 스레드란 무엇인지에 대한 질문을 받았다고 생각하고 스레드를 정의해보라.

 

A2 : 스레드는 개발자에게는 작업을 만드는 단위이고 멀티스레드 운영체제에게는 실행단위이자 CPU를 할당하는 스케줄링 단위이다.

 

 

Q3 : 스레드의 컨텍스트란 어떤  정보를 말하는가 ? 그리고 어디에 저장되는가 ?

 

A3 : 스레드 컨텍스트란 스레드가 현재 실행중인 일체의 상황을 말한다. 그 일체의 상황은 CPU 내의 레지스터들과 메모리에 고스란히 담겨 있다. 현재 실행중인 컨텍스트는 CPU 레지스터에 있으며 컨텍스트 스위칭이 일어나면 TCB에 중단된 스레드의 컨텍스트가 저장된다.

 

 

Q4 : 스레드의 주소 공간은 어디에 만들어지는가 ? 하나의 스레드가 실행되기 위해 주어지는 주소공간은 총 6개의 영역으로 나누어진다. 이 6가지가 무엇인지 설명하라. 이 중에서 다른 스레드와 공유하는 공간은 무엇인가 ? 

 

A4 : 스레드 주소 공간은 프로세스의 주소 공간 내에 형성된다. 프로세스에 속한 모든 스레드들이 프로세스의 주소공간을 나누어 사용한다. 스레드 주소 공간은 프로세스의 주소 공간 내에서 구체적으로 스레드의 사적공간과 스레드 사이의 공유 공간으로 나누어진다

 

스레드 사적 공간

각 스레드가 사용하는 사적 공간
1. 스레드 코드(Thread code)
2. 스레드 로컬 스토리지(TLS, Thread Local Storage)
3. 스레드 사용자 스택(Thread Sser Stack)과 스레드 커널 스택(Thread Kernel Stack)

스레드 사이의 공유 공간

프로세스에 속한 모든 스레드들이 공유하는 공간
4. 프로세스에 선언된 함수 코드
5. 프로세스의 데이터 공간(로컬 스토리지 제외)
6. 프로세스의 힙 공간

 

 

스레드 주소 공간에 대한 설명

- 스레드 코드 영역
프로세스의 함수가 스레드의 코드가 되기 때문에 스레드의 코드영역은 프로세스 코드영역내에 있다. 프로세스의 코드영역에 적재된 함수들은 모든 스레드가 호출하여 사용할 수 있다.
- 스레드 데이터 영역
스레드가 사용할 수 있는 데이터 공간은 두 부분인데, 모두 프로세스의 데이터 공간 내에 형성된다.
첫째, 스레드는 자신만 사용할 수 있는 전역변수를 선언할 수 있는데, 이 공간이 스레드 전용 전역변수공간이며, 스레드 로컬 스토리지라고 부른다. 스레드 전역변수를 선언하려면 static_trhread와 같은 특별한 키워드를 사용하면 된다.
둘째, 프로세스에서 선언된 모든 전역 변수들은 프로세스의 모든 스레드들에 의해 공유된다. 스레드들이 상호 데이터를 주고받기 위한 공간으로 사용된다.
- 스레드 힙 영역
프로세스의 힙은 프로세스에 속한 모든 스레드들이 동적 할당받아 사용할 수 있는 힙 공간으로 공유된다. 한 스레드가 할당받은 동적 메모리는 그 주소만 알면 다른 스레드가 접근할 수 있어, 힙은 스레드간에 데이터를 주고받는 통신장소로도 사용된다.
- 스레드 스택영역
프로세스의 사용자 스택은 스레드가 생길때마다 스레드스택으로 나누어 할당된다. 커널공간에도 사용자 스레드가 사용할 수 있는 공간이 있는데 스레드가 시스템 호출을 통해 커널모드로 진입하면 자동으로 커널 메모리의 일부를 스레드 스택으로 사용하도록 할당된다. 이를 커널 스택이라고 부르는데 커널 스택은 스레드가 커널 코드를 실행하는 동안 함수 호출 등에 사용되고 시스템호출에서 돌아올 때 사라진다.

 

 

Q5 : 프로세스1에 속한 스레드에서 프로세스2가 속한 스레드로 컨텍스트 스위칭이 일어날 때, 같은 프로세스에 속한 스레드 스위칭의 경우보다 추가적으로 더 필요한 작업은 무엇인가 ? 

 

A5 : 프로세스의 논리주소와 물리주소를 매핑하는 MMU(Memory Management Unit) 장치내에 들어 있는 프로세스1의 매핑테이블을 제거하고 프로세스의2의 매핑테이블을 적재해야한다. 또한 CPU내에 TLB버퍼를 모두 초기화 하고 프로세스2의 TLB를 채우는 과정이 필요하고 프로세스가 바뀌기 때문에 CPU캐시에 담긴 코드와 데이터도 모두 무력화 시켜야한다. 그 다음 새 프로세스의 스레드가 실행을 시작하면 CPU캐시에 스레드 코드와 데이터가 없기 때문에 계속 캐시 미스가 발생하여 캐시가 채워지기 전까지 상당한 시간이 소요된다.

 

 

Q6 : 우리가 멀티스레딩 프로그램을 작성할 때 사용자 레벨 스레드로 만들면 어떤 장점이 있으며, 커널 레벨 스레드로 만들면 어떤 장점이 있는가 ? 

 

A6 : 사용자 레벨 스레드로 만들면 커널의 도움을 받지 않고 스레드 라이브러리의 도움을 받기 때문에 스레드를 지원하지 않는 운영체제에서도 멀티스레드 응용프로그램을 작성할 수 있고 커널 레벨 스레드보다 컨텍스트 스위칭과 이식성면에서 우월하다. 반면 멀티코어 CPU를 가진 컴퓨터에서는 커널 레벨 스레드로 만들면 서로 다른 스레드들이 다른 코어에서 병렬적으로 실행 될 수 있고 하나의 스레드가 중단(blocked)된다고 해도 사용자 레벨 스레드는 프로세스의 모든 사용자 레벨 스레드가 중단되는 것과는 다르게 커널 레벨 스레드는 해당 스레드만 중단된다는 장점이 있다.

 

 

Q7 : 내가 작성한 함수는 사용자 영역에 주소공간이 있다. 이 함수가 커널 레벨 스레드로 실행될 수 있는가 ? 없다면 이유를, 있다면 실행되기 위해 개발자가 취해야할 필요한 조치는 무엇인가 ?

 

A7 : 커널 레벨 스레드로 실행가능하다. 커널 레벨 스레드가 커널에 의해 스케줄링 된다는 의미이지 커널 레벨 스레드의 코드와 데이터가 커널에 있어야 한다는 것은 아니기 때문이다. 커널 레벨 스레드로 사용하려면 시스템 호출을 통해 스레드를 생성하여야한다.

 

 

Q8 : N : 1 매핑은 멀티스레드 응용프로그램에서 만든 스레드 중 한 개가 파일 입출력으로 블록상태가 되면 응용프로그램내의 모든 스레드가 스케쥴되지 못한다. 이 과정을 그림으로 그리고 간단히 설명하라.

 

A8 :

read()함수를 호출하게되면 커널코드를 실행되고 디스크를 읽어오는 동안 block상태로 만든다. 커널레벨 스레드인 TCB4는 블록상태로 표시되고 커널의 스케줄러가 다른 커널 레벨 스레드에게 CPU를 할당하게 된다. 그림을 보면 유저레벨 스레드인 U-TCB2는 사용자 스레드 코드 2를 실행하는 과정에서 파일 입출력을 위해 block상태가 되었고 나머지 유저 레벨 스레드는 블록 상태가 되지 않았음에도 커널 레벨 스레드인 TCB4는 나머지 전체가 블록상태가 된것으로 판단하여 다른 커널 레벨 스레드인 TCB3에게 CPU를 할당해주는 것을 볼 수 있다.

 

 

 

Q9 : 1 : 1 매핑이 오늘날 컴퓨터 시스템에서 많이 사용되는 이유가 무엇인지 설명하라.

 

A9 : 멀티코어 CPU나 멀티 프로세서를 가진 현대의 컴퓨터에선 높은 병렬성을 가지는 1 : 1 매핑이 다른 매핑보다 더 적합하기 때문이다.

 

 

Q10 : 교재의 복합문제1의 멀티스레드 응용프로그램을 작성하라.  문제는 다음과 같다.

그림 4-5의 맛보기 프로그램을 참고하여, 리눅스에서 다음 5가지 조건에 부합하는 멀티스레드 C 응용프로그램을 작성하라. 4개의 스레드를 활용하여 1에서 40000까지의 합을 구하여 출력하라.

(1) pthread라이브러리를 이용하여 작성하라.

(2) 응용프로그램에 전역변수 int sum[4]을 선언하라.

(3) 스레드로 실행할 함수의 이름을 runner로 하라.

(4) main()에서 4개의 스레드를 연속적으로 생성하여 다음과 같이 이용하라. 4개의 스레드는 동시에 실행된다.

스레드 1 : 1-10000까지의 합을 구하고 sum[0]에 저장
                  pthread_create(..., ..., runner, "1"); // 1에서 10000까지 합 구하기
스레드 2 : 10001-20000까지의 합을 구하고 sum[1]에 저장
            pthread_create(..., ..., runner, "10001"); // 10001에서 20000까지 합 구하기
스레드 3 : 20001-30000까지의 합을 구하고 sum[2]에 저장
            pthread_create(..., ..., runner, "20001"); // 20001에서 30000까지 합 구하기
스레드 4 : 30001-40000까지의 합을 구하고 sum[3]에 저장
            pthread_create(..., ..., runner, "30001"); // 30001에서 40000까지 합 구하기

(5) main 스레드는 4개의 스레드가 모두 종료하기를 기다린 후 sum[]배열의 값을 모두 합쳐 그 결과를 화면에 출력하라. 다음은 이 프로그램의 이름이 prac4_1.c라고 할 때 컴파일 과정과 실행 결과를 보여준다.

$gcc -o prac4_1 prac4_1.c -lpthread
$./prac4_1
1에서 40000까지 4개의 스레드가 계산한 총 합은 800020000

 

A10 :

//prac4_1

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

void* runner(void *param);
int sum[4] = {0,};
char num[4][20];

int main(){

	pthread_t tid[4];
	pthread_attr_t attr[4];
	int i;
    
	for(i = 0; i < 4; i++){
		sprintf(&num[i][0], "%d", 1 + 10000 * i);
		pthread_attr_init(&attr[i]);
		pthread_create(&tid[i], &attr[i], runner, &num[i][0]);
	}

	for(i = 0; i < 4; i++)
		pthread_join(tid[i], NULL);
	
    printf("sum = %d\n", sum[0] + sum[1] + sum[2] + sum[3]);
}

void* runner(void *param){

	int to = atoi(param);
	int i, j = to / 10000;
    
	for(i = to; i <= to + 9999; i++)
		sum[j] += i;
        
}

 

 

Q11 : 교재의 복합문제2의 멀티스레드 응용프로그램을 작성하라.  문제는 다음과 같다.

앞의 문제를 수정하여 리눅스에서 다음 5가지 조건에 부합하는 멀티스레드 C 응용프로그램을 작성하라. 4개의 스레드를 활용하여 1에서 40000까지의 합을 구하여 출력하라.

(1) pthread라이브러리를 이용하여 작성하라.

(2) 응용프로그램에 전역변수 int sum을 선언하라.

(3) 스레드로 실행할 함수의 이름을 runner로 하라.

(4) main()에서 4개의 스레드를 연속적으로 생성하여 다음과 같이 이용하라. 4개의 스레드는 동시에 실행된다.

스레드 1 : 1-10000까지의 합을 구하고 sum에 저장
                  pthread_create(..., ..., runner, "1"); // 1에서 10000까지 합 구하기
스레드 2 : 10001-20000까지의 합을 구하고 sum에 저장
            pthread_create(..., ..., runner, "10001"); // 10001에서 20000까지 합 구하기
스레드 3 : 20001-30000까지의 합을 구하고 sum에 저장
            pthread_create(..., ..., runner, "20001"); // 20001에서 30000까지 합 구하기
스레드 4 : 30001-40000까지의 합을 구하고 sum에 저장
            pthread_create(..., ..., runner, "30001"); // 30001에서 40000까지 합 구하기

(5) main 스레드는 4개의 스레드가 모두 종료하기를 기다린 후 sum값을 모두 합쳐 그 결과를 화면에 출력하라. 다음은 이 프로그램의 이름이 prac4_2.c라고 할 때 컴파일 과정과 실행 결과를 보여준다.

$gcc -o prac4_2 prac4_2.c -lpthread
$./prac4_2
1에서 40000까지 4개의 스레드가 합친 sum 변수의 값은 583862451
$ ./prac4_2
1에서 40000까지 4개의 스레드가 합친 sum 변수의 값은 640209990
$ ./prac4_2
1에서 40000까지 4개의 스레드가 합친 sum 변수의 값은 500849160

1에서 40000까지의 더한 합은 문제 1의 결과에 따라 800020000인데, 화면에는 800020000가출력되지 않는다. 여러 번 실행해도 계속 다른 값이 출력된다. 그 이유는 무엇인지 나름대로 설명해보라. 

 

A11 :

//prac4_2

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

void* runner(void *param);
int sum = 0;
char num[4][20];

int main(){

	pthread_t tid[4];
	pthread_attr_t attr[4];
	int i;
    
	for(i = 0; i < 4; i++){
		sprintf(&num[i][0], "%d", 1 + 10000 * i);
		pthread_attr_init(&attr[i]);
		pthread_create(&tid[i], &attr[i], runner, &num[i][0]);
	}

	for(i=0; i<4; i++)
		pthread_join(tid[i], NULL);
	
    printf("sum = %d\n", sum);
    
}

void* runner(void *param){

	int to = atoi(param);
	int i, j = to / 10000;
    
	for(i = to; i <= to + 9999; i++)
		sum += i;
        
}

prac4_2.c의 전역변수 sum은 스레드들 사이의 공유데이터이다. 여러 스레드들이 공유 데이터인 sum에 동시에 접근했기 때문에 스레드들이 공유데이터의 값이 제대로 저장되기 전에 값을 읽거나 사용하여 원래의 의도와는 다른 값이 출력된것이다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.