[OS] 04. Thread and Cocurrency

SYiee·2022년 12월 31일
0

🦕Operating System

목록 보기
4/14
post-thumbnail

Process - in Previous Chapter

✅ 단점

  • Heavy-weight
    → address space 크고 os resource를 많이 잡아먹고 PCB의 크기가 크다
  • 여러개의 프로세스를 동시에 돌아가는 것처럼 보이게 하기 위해 스케쥴링을 사용하는데 오베헤드가 크다.
  • 새 프로세스를 만드는 것이 costly → all of the data structures must be allocated and initialized
  • IPC를 할 때도 커널에 도움을 받아야하니 비용이 많이 든다

Thread Concept: Key Idea

💁🏻‍♀️ 한 프로세스 안에서 패스를 여러갈래로 만들어 보자..!
단일 프로세스 안에서 여러 개의 execution path를 만들어서 fork 사용하는 것보다 가볍게 동작하면서도 멀티프로세스한 것과 비슷하게 만들 수 있을 것 같다.

  • 기존에 IPC를 할 때 다른 프로세스와 협력하기 위해서 커널을 거쳐야만 했다면 멀티 스레드를 사용하면 한 프로세스 안이기 때문에 커널을 거치지 않아 오버헤드도 줄어들 수 있다.
  • 스케쥴링 개수가 줄어드니 자신의 차례가 돌아올 확률도 높아짐
  • context switch에서 발생하는 오버헤드도 줄일 수 있다

Single and Multithreaded Processes

■ Single-threaded process

→ 하나의 프로세스가 한 번에 하나의 작업만 수행하는 것

■ Multithreaded process

→ 하나의 프로세스가 동시에 여러 작업을 수행하는 것

멀티프로그래밍 시스템이니까 프로세스를 여러개 돌려도 되는데 굳이 스레드를 나누는 데는 이유가 있다.

  1. 두 프로세스가 하나의 데이터를 공유하려면 메시지 패싱이나 공유 메모리 또는 파이프를 사용해야 하는데, 효율도 떨어지고 개발자가 구현, 관리하기도 번거롭다.
  2. 프로세스 사이 컨텍스트 스위치가 계속 일어나면 성능 저하가 발생한다. 스레드 전환에도 컨텍스트 스위치가 일어나지만 속도가 더 빠르다.

📌single보다 좋은점

  • sindgle은 하나의 패스를 위해서 모든 것을 소지하고 있어야 하는데 멀티스레드는 공유해서 사용

Address Space with Threads

  • code segment, static data, heap은 스레드끼리 공유하고
  • PC, SP와 같은 레지스터나 stack은 각각 다른 값을 가진다

Concurrent Servers: Multiprocess Model

■ Web server example

멀티프로세스로 만든 이유

  • 클라이언트가 서버에게 요청을 보내면 서버는 새로운 스레드를 하나 생성해 요청을 수행한다. 프로세스를 생성하는 것보다 스레드를 생성하는 것이 더 빠르기 때문이다.
  • 싱글 프로세스로 만들면 요청이 들어온거에 대해 다 처리해줄 때까지 다음 요청을 받을 수 없으니까 응답시간이 길어져서 원활하게 서비스를 제공해 줄 수 없기 때문 (응답시간을 높이기 위해)

Concurrent Servers: Multiprocess → Multithread

Concurrent Servers: Multithread Model

■ Using threads

We can create a new thread for each request

Single-Process (Iteration)

#include <stdio.h>
#define MAX_CMD 256
void DoCmd(char *cmd)
{
printf("New command: %s\n", cmd);
sleep(1);
printf("Done\n");
}
int main()
{
char cmd[MAX_CMD];
while (1) {
printf("CMD> ");
fgets(cmd, MAX_CMD, stdin);
if (cmd[0] == 'q')
break;
DoCmd(cmd);
}
return 0;
}

Multi-Processes

#include <stdio.h>
#define MAX_CMD 256
void DoCmd(char *cmd)
{
printf("New command: %s\n", cmd); 
sleep(1); printf("Done\n"); 
exit(0);
}
int main()
{
char cmd[MAX_CMD]; int pid;
while (1) {
printf("CMD> "); 
fgets(cmd, MAX_CMD, stdin);
if (cmd[0] == 'q') break;
if ((pid = fork()) == 0) {
DoCmd(cmd);
}
#if 1
else { wait(pid); }
#endif
}
return 0;
}

Multi-Threads

#include <stdio.h>
#include <pthread.h>
#define MAX_CMD 256
void DoCmd(char *cmd) 
{
printf("New command: %s\n", cmd);
sleep(1); printf("Done\n");
pthread_exit(NULL);
}
int main() 
{
char cmd[MAX_CMD]; pthread_t tid;
while (1) {
printf("CMD> "); 
fgets(cmd, MAX_CMD, stdin);
if (cmd[0] == 'q') break;
pthread_create(&tid, NULL,(void *)DoCmd, (void *)cmd);
#if 1
pthread_join(tid, NULL);
#endif
}
return 0;
}

Makefile

CC = gcc
CFLAGS =
LDFLAGS =
LIB = -lpthread
OBJ1 = iteration.o
OBJ2 = process.o
OBJ3 = thread.o
TARGET = iteration process thread
.SUFFIXES: .c .o
.c.o:
$(CC) $(CFLAGS) -c $<
default: $(TARGET)
iteration: $(OBJ1)
$(CC) -o $@ $(LDFLAGS) $(OBJ1)
process: $(OBJ2)
$(CC) -o $@ $(LDFLAGS) $(OBJ2)
thread: $(OBJ3)
$(CC) -o $@ $(LDFLAGS) $(OBJ3) $(LIB)
clean:
rm -f *.o $(TARGET)

Multicore Programming

📌 Concurrent execution on a single-core system
: 병렬연산의 일종, 코어가 하나일 때는 그렇게 보이게 함

동시성(Concurrency) : 프로세서가 여러 개의 스레드를 번갈아가며 수행함으로써 동시에 실행되는 것처럼 보이게 하는 방식

📌 Parallel execution on a multicore system
: 멀티코어인 상황에서 process를 나누어서 수행

병렬성(Parallelism) : 멀티코어 시스템에서 사용되는 방식으로, 여러 개의 코어가 각 스레드를 동시에 수행하는 방식

  • Data vs. Task parallelism
    → 무엇을 기준을 삼을지에 따라 다름
    Data parallelism - data에 조점 : data를 나누어서
    Task parallelism - path에 초점: data를 공유해서 쓰지만 패스별로 나누어서 사용

Parallel Programming

  • Task Parallelism
    ✅ Pthreads (POSIX threads)

  • Data Parallelism

    ✅ OpenMP (Open Multi-Processing)
    ✅ SIMD (Single Instruction Multiple Data)
    ✅ SIMD (Single Instruction Multiple Data)
    ✅ GPGPU (General Purpose computing on GPUs)

    • CUDA (Compute Unified Device Architecture)
    • OpenCL (Open Computing Language)

Thread의 발전 과정

User Threads : 사용자 수준의 스레드 라이브러리가 관리하는 스레드

📌 초창기

  • 처음부터 커널에 스레드 개념을 집어 넣기 쉽지 않아 library형태로 제작을 하였다.
  • 온전한 의미의 멀티스레딩이 아니라고 볼 수 있다

✅ 장점

  • OS 입장에서는 신경을 안 써도 되니 오버헤드가 적다

✅ 단점

  • 안정성이 떨어진다.
  • 한 프로세스 안에 1, 2, 3 스레드 3개가 있는데 만약 3번 스레드가 커널에게 trap을 걸어 I/O요청을 했을 때 3번 스레드 만이 아니라 1~3 스레드 전부가 waiting queue에 들어가게 된다.

Kernel Threads : 커널이 지원하는 스레드

: 최신 OS들은 모두 지원

✅ 장점

  • 기능상 커널에서 관리 하는 것이 더 신뢰성 있고 안정적
  • 진정한 의미의 멀티스레드

✅ 단점

  • 유저 모드에서 커널 모드로 계속 바꿔줘야 하기 때문에 성능이 저하

Multithreading Models

  • Many-to-One
    : 하나의 커널 스레드에 여러 개의 유저 스레드를 연결하는 모델이다. 한 번에 하나의 유저 스레드만 커널에 접근할 수 있기 때문에 멀티코어 시스템에서 병렬적인 수행을 할 수가 없다.
    • 커널은 단일 패스로만 동작

  • One-to-One
    : 하나의 유저 스레드에 하나의 커널 스레드가 대응하는 모델이다. 동시성을 높여주고, 멀티프로세서 시스템에서는 동시에 여러 스레드를 수행할 수 있도록 해준다. 유저 스레드를 늘리면 커널 스레드도 똑같이 늘어나는데, 커널 스레드를 생성하는 것은 오버헤드가 큰 작업이기 때문에 성능 저하가 발생할 수 있다.
    • 유저 레벨 스레드당 커널 레벨 스레드가 각각 하나씩 맵핑
    • 대표적으로 윈도우가 사용하는 모델

  • Many-to-Many
    : 여러 유저 스레드에 더 적거나 같은 수의 커널 스레드가 대응하는 모델이다. 운영체제는 충분한 수의 커널 스레드를 만들 수 있으며, 커널 스레드의 구체적인 개수는 프로그램이나 작동 기기에 따라 다르다. 멀티프로세서 시스템에서는 싱글프로세서 시스템보다 더 많은 커널 스레드가 만들어진다.
    • n대 n
    • 리눅스 계열

Threading Issues

2개의 버전의 fork - fork() exec()을 실행했을 때
개발자들이 1번이나 2번 방법 중에 선택을 해서 fork를 해야하는데 현장에서는 멀티스레드로 만들어진 application 안에서는 fork를 하지 않는다.

Threads의 종류

📢 언어마다 플랫폼마다 멀티스레드를 구현하는 것이 다 제각각이다.

Pthreads (POSIX - Potable Operating System)

  • Unix 계열, C 스타일
  • int pthread_create(...) : create pthread
  • void pthread_exit (...) : exit pthread
  • int pthread_join (...) : 멀티프로세스에서 wait과 같음

Windows Threads

  • C 스타일

Java Threads

  1. thread라는 클래스가 존재하고 얘를 상속받은 새로운 클래스를 받은 다음에 run()함수를 오버라이딩해서 멀티스레드환경에서 동작해야하는 것들을 구현
  2. runnable이라는 인터페이스를 새로운 클래스로 구현해서 사용
profile
게임 개발자

2개의 댓글

comment-user-thumbnail
2023년 10월 12일

wow? fantastic posting <3 i love it

1개의 답글