Computer Science/Operating System

[운영체제] Thread

HJChung 2021. 2. 9. 10:48

1. Processs vs Thread

Process

컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램, 즉 메모리에 올라와 실행되고 있는 독립적인 프로그램 인스턴스

Thread

프로세스 내에서 동작되는 여러 실행의 흐름 단위

  • Processs는 독립적, Thread는 프로세스의 서브넷
  • Process는 각각  독립적인 자원을 가진다. Thread는 프로세스의 자원을 공유한다. 
  • Process는 자신만의 주소 영역을 가진다. Thread는 주소 영역을 공유한다.
  • Process간에는 IPC기법으로 통신해야하지만 Thread는 필요없다. (IPC에 대해서는 아직 공부가 더 필요한 상태..)

2. Thread

- Light Weight Process라고도 함

- 프로세스 간에는 각 프로세스의 데이터 접근이 불가하다. (IPC를 사용해야함)

그러나 스레드는

- 하나의 프로세스에 여러개의 스레드 생성이 가능하다. 

- 여러 스레드들이 동시에 실행가능

- 프로세스가 여러개의 스레드로 된 것이므로 프로세스의 데이터를 모두 접근 가능하다.  (IPC를 사용할 필요가 없음)

 

3. Thread의 동작 방식

 

 

이렇게 하나의 프로세스 내에서 생성된 thread끼리 해당 프로세스의 영역을 공유한다.는 특징이 있다. 

 

 

 

4. 멀티 태스킹과 멀티 프로세싱

 

 

즉, 아래와 같이 다양한 방식으로 실행하게 운영체제를 구성할 수 있다. 

 

 

 

5. Thread의 장단점

1) 장점

1. 사용자에 대한 응답성 향상

어떤 process가 두 역할을 한다고 가정해보자. 첫 번째 기능은 1~100만까지 더하는 기능, 2 번째 기능은 지금까지 더한 수를 전달해 주는 기능

이 두 기능을 하나의 process로 만들면 코드 상 1~100만까지 다 더하는 for문 코드가 다 돌고 나서 2번째 기능의 코드가 실행될 가능성이 크다. 그러면 응답시간이 길어진다. 

하지만 첫번째 기능을 thread1, 두번째 기능을 thread2로 나눠서 생각하면 멀티 태스킹이나 멀티 프로세싱(병렬 처리)로 다른 cpu 코어에서 동시 처리가 가능해진다. 그러면 응답시간이 짧아질 수 있다. 

 

 

2. 자원공유의 효율성

thread는 하나의 프로세스 내에서 자원 공유가 가능하기 때문에 프로세스의 데이터를 모두 접근 가능하다. 

각각의 프로세스로 나뉘어져있다면 IPC기법과 같이 프로세스간 자원 공유를 위해 번거로운 작업이 필요없다. 

추가적으로, 만약 동시 실행을 위해 6개의 프로세스를 만들었다면 6개의 프로세스 공간이 필요해진다. (하나의 프로세스당 4G라면 총 24G) 

그런데 하나의 프로세스당 6개의 쓰레드를 만들면 4G만 있으면 된다. (자원 효율성)

 

3. 작업이 분리되어 코드가 간결하지만 이는 코드를 어떻게 작성하느냐에 따라 달라지는 문제이긴 하다. 

2) 단점

1. thread는 하나의 프로세스 안에서 작동하기때문에 thread중 한 thread만 문제가 생겨도, 전체 프로세스가 영향을 받는다. 

 

 

2. thread를 많이 생성하면, Context Switching이 많이 일어나 성능이 저하되기도 한다.

예를 들어, Linux OS에서는 Thread를 Process와 거의 동일하게 다룬다. 그래서 thread역시 많이 생성하면 모든 Thread를 스케쥴링 해야하므로 Context Switching이 빈번할 수 밖에 없다.  

 

6. Thread의 동기화(Synchronization) 이슈

동기화 이슈로 인해 쓰레드 관리가 필요하다.

예)

#python code

import threading #쓰레드를 지원하는 라이브러리

q_count = 0

def thread_main():
    global g_count
    for i in range(10000):
    	g_count = g_count + 1
        
threads = []

for i in range(50):
    th = threading.Thread(target = thread_main) #쓰레드로 실행할 함수명을 target에 넣어주어서 해당 함수를 쓰레드로 만들어서 실행시킨다. 
    threads.append(th)
    
for th in threads:
	th.start()
    
for th in threads:
	th.join()
    
print('g_count = ', g_count) #실행 결과 g_count = 500000

for i in range(50)에서 총 50개의 thread중 30개의 thread만 실행완료되고, 나머지 20개의 thread는 아직 실행 중일수도 있는데, 

그럼에도 불구하고 main thread인 print('g_count = ', g_count)가 실행 되어버릴 수도 있다. 

그래서 필요한 것이

for th in threads: th.join() 코드로, join()함수를 통해 thread가 다 끝날 때까지 기다리는 코드이다. (thread간의 동기화(기간을 맞추는)를 시키는 함수)

 

그런데, range가 100000이상 증가할 경우, 또 g_count가 비정상적인 값으로 나오게 된다. 왜일까?

 

 

 

그래서 위의 코드 중 해결해야 하는 것은 '하나의 thread에서 'for i in range(100000): g_count = g_count+1' 에 해당하는 코드가 중간에 다른 thread로 바뀌지 않고 완전히 완료되어야 한다는 것이다. 그래서 코드를 아래와 같이 수정해서 문제를 해결 할 수 있다. 

#python code
#mutual exclusion

import threading #쓰레드를 지원하는 라이브러리

q_count = 0

def thread_main():
    global g_count
    lock.acquire() #2
    for i in range(10000):
    	g_count = g_count + 1
    lock.release() #3

lock = threading.Lock() #1
threads = []

for i in range(50):
    th = threading.Thread(target = thread_main) #쓰레드로 실행할 함수명을 target에 넣어주어서 해당 함수를 쓰레드로 만들어서 실행시킨다. 
    threads.append(th)
    
for th in threads:
	th.start()
    
for th in threads:
	th.join()
    
print('g_count = ', g_count) #실행 결과 g_count = 500000

 

지금까지

thread 의 개념, 장단점, 동기화 문제 등을 공부하고 정리해 보았다. 

다음에는 thread와 관련된 세마포어와 deadlock, starvation등을 공부하고 정리해볼 것이다.