CS/운영체제

[운영체제] 7. Concurrency

공영재 2023. 10. 7. 16:21

Reference - Operating Systems: Three Easy Pieces

 

 

https://pages.cs.wisc.edu/~remzi/OSTEP/

 

Operating Systems: Three Easy Pieces

Blog: Why Textbooks Should Be Free Quick: Free Book Chapters - Hardcover - Softcover (Lulu) - Softcover (Amazon) - Buy PDF - EU (Lulu) - Buy in India - Buy Stuff - Donate - For Teachers - Homework - Projects - News - Acknowledgements - Other Books Welcome

pages.cs.wisc.edu

 

이번 단원에서는 운영체제의 3가지 핵심 중 두번째인 Concurrency에 대해 다뤄보겠다.

Concurrency란 동시성을 말하는데, 여러 쓰레드를 동시에 실행하고 이때 발생하는 문제들에 관한 내용이다. 구현 시 발생하는 문제를 해결하기 위해 사용되는 Lock, Semaphore, deadlock에 대해서도 이후 포스팅에서 알아볼것이다.

Thread

스레드란 프로세스와 거의 유사한 개념이나, '한 프로세스 내에서 여러개의 스레드가 실행된다'는 하위 구조라 생각하면 편하다. 무엇보다 중요한 점은 독립적인 메모리 공간을 가지는 프로세스와 달리 스레드는 한 프로세스에서 실행되므로 메모리를 공유한다는 차이가 있다. 메모리를 공유하면 구현이 쉬워지고 context switch에 따른 overhead가 줄어든다는 이점이 있다.

스레드를 사용하는 이유는 병렬 처리가 간단해지고, 스레드간 context switch를 통해 I/O를 원할히 수행할 수 있다.

스레드는 pthread라는 라이브러리로 생성할 수 있으며, PCB처럼 TCB가 존재하여 thread state를 저장한다. 

 

멀티 스레드의 구조는 아래 오른쪽 그림과 같다. program code나 heap은 공유하지만, 스레드마다 stack 공간은 따로 있다.

 

 

위처럼 프로세스와 다른 메모리 구조를 가질 때, 프로그램 실행을 어떻게 처리할 것인가?

 

먼저 스레드의 중요한 문제점들을 살펴보자.  A스레드와 B스레드를 생성하고 출력한 뒤 종료하는 코드가 있을 때, 기존 C언어 프로그램은 A와 B를 순차적으로 출력한 뒤 종료될 것이다. 하지만 스레드의 경우 다르다. A와 B의 순서가 랜덤으로 출력될 것이다. 왜 이러한 문제가 발생할까?

 

 

스레드는 OS의 스케줄러에 의해 실행순서가 결정된다. OS 스케줄링에서 배웠듯이 어떤 스레드가 먼저 실행될지는 여러 변수로 인해 개발자가 알아채기 힘들기에, 위와 같이 순서가 바뀔 수 있다. 즉, 스레드가 실행되는 순서는 알 수 없다.

 

또다른 문제는 메모리를 공유한다는 점이다. 전역변수 counter를 만들고 A, B 스레드를 생성한 뒤 각각 1을 100만번씩 더하는 코드가 있을 때, 예상과 달리 결과값은 200만에 훨씬 못미칠 것이다.

그 이유는, 어셈블리어 관점에서 덧셈이 수행되는 방식을 봤을 때 메모리의 전역 변수를 가지고 온 뒤 레지스터에 넣고, 레지스터에서 덧셈을 수행한 뒤 다시 메모리에 넣는 방식이기 때문이다.

저 과정 도중에 time sharing 등의 interrupt로 context switch가 발생한다면, A 스레드가 레지스터에서 더한 값을 메모리에 옮기지 못한 채로 B 스레드가 다시 덧셈을 수행하게 되어 1을 두번 덮어쓰는 등의 문제가 발생한다.

 

이처럼, 전역 변수와 같이 스레드에서 공유하는 데이터에 접근하는 것을 race condition(경쟁 상태)라 하고, 공유하는 데이터를 critical section(임계 영역)이라 한다. 스레드를 아무 조건없이 실행하게 되면, 위처럼 임계 영역에 여러 스레드가 접근하는 경쟁 상태가 생겨 원하는 값을 얻지 못한다. 이를 방지하기 위해 임계 영역에 한 스레드가 접근할 때 다른 스레드의 접근을 막는 mutal exclusion(상호 배제)가 있다.

상호 배제를 위해 Mutex(lock), Semaphore, Condition variables(Pthreads), Monitors(Java) 등이 사용된다.

mutex의 사용방법은 아래와 같다.

 

loading