Multi Threading

안준성·2024년 7월 28일
0

OperatingSystem

목록 보기
18/22
post-thumbnail

멀티 스레딩이란 말 그대로 한 프로세스 내에서
여러 실행 흐름(스레드)을 동시에 실행하여
작업을 병렬적으로 처리하는 기법이다.

요즘 같이 cpu의 성능이 좋아진 시대에서 cpu의 성능을 최대한 끌어내려면
멀티 스레딩은 어쩌면 필수라고도 볼 수 있다.

그렇다면 스레드가 뭐길래 병렬적으로 실행 가능하다는 걸까?
스레드에 대해 다들 알겠지만 조금 더 깊이 들어가보자.


Thread

user-level과 kernel-level

스레드는 크게 user-level threadkernel-level thread 두 가지 방식으로 구현된다.

우리가 보통 사용하는 스레드는 사용자 수준 스레드이며, 스레드 라이브러리에 의해 관리된다.
이것만으로는 실제 병렬적으로 동작하지 않고 병행으로 눈속임만 할 뿐이다.

실제 병렬적으로 동작할 수 있는건 커널 수준 스레드이고
사용자 수준 스레드를 병렬적으로 동작하게 하려면
커널 수준 스레드와 매핑을 시켜야 한다.

사용자 수준 스레드는 진짜 스레드가 아니기 때문에
만약 한 스레드가 블로킹 호출을 하면 다른 스레드들도 블로킹 된다.
(사용자 수준 스레드는 실제 병렬이 아닌 병행으로 동작하고 있었기 때문에)

커널 수준 스레드는 커널이 관리하며, 각 스레드는 커널 객체로 존재한다.
커널 수준 스레드는 하드웨어의 프로세서코어에 대응되어 진정한 병렬 실행이 가능하다.

커널 수준 스레드만 쓰면 되지 왜 사용자 수준 스레드를 쓰는 것일까?

일단 커널 수준 스레드는 말 그대로 커널 수준이기 때문에
사용할 때마다 커널 모드와 사용자 모드 간의 전환이 일어난다.
이는 시간과 자원을 소모하는 작업이다.

또한 실제 하드웨어에 대응되기 때문에 많은 스레드를 생성하면 많은 자원이 소모되며,
실제 물리적인 코어 개수만큼의 스레드까지밖에 생성하지 못한다.

이를 추상화하여 나온게 사용자 수준 스레드로,
이는 진정한 스레드는 아니지만 실제 커널 수준 스레드와 N:M 매핑되어
개발자가 느끼기에는 실제 스레드처럼 사용할 수 있다.

또한 이는 가상이기 때문에 개수에 한계가 없고,
아무리 많이 만들어도 커널의 자원을 사용하지 않는다.
(물론 너무 많이 만들면 컨텍스트 스위칭만 하다가 끝나게 된다)


동기화

스레드는 하나의 프로세스 내에서 프로세스 메모리의 코드 영역, 데이터 영역, 힙 영역을 공유하며,
개별적인 program counter와 stack, TCB(스레드 제어 블록)등을 가진다.
이러한 특성때문에 스레드 간에는 빠른 통신과 데이터 공유가 가능하지만,
동기화와 race condition같은 문제를 야기할 수 있다.

여기서 스택 영역은 각 스레드가 독립적으로 가지는데,
이 때문에 지역 변수와 같은 스택에 쌓이는 데이터에 대해선
동기화 문제를 신경 쓸 필요가 없다.

이와 달리 힙 영역의 객체들이나 전역 변수 등에 write 작업을 할 시에는 동기화 문제를 신경 써야 하는데,
이를 방지하기 위한 여러 장치들이 존재한다.

Mutex

뮤텍스는 상호 배제라는 이름처럼 공유 자원에 대한 접근을 제한하여
동시에 하나의 스레드만 공유 자원에 접근할 수 있게 하는 장치이다.
이는 커널 수준에서 구현되며, 잠금과 해제를 통해 임계 구역을 보호한다.

mutex의 동작 원리

뮤텍스는 내부적으로 소유자와 상태, 대기 큐를 가지는 자료구조로,
현재 뮤텍스를 소유하고 있는 스레드의 ID를 저장한다.
뮤텍스가 잠긴 동안 접근을 시도한 스레드는 대기 큐에 넣어
뮤텍스가 해제 됐을 때 대기 중인 스레드 중 하나를 깨운다.

실제 뮤텍스를 이용한 공유자원 접근 시나리오를 살펴보면

  1. 스레드가 mutex lock을 얻기 위해 시도한다.
  2. 커널은 mutex의 현재 상태를 확인한다.
  3. mutex가 unlocked 상태라면 locked 상태로 변경하고 현재 스레드를 소유자로 설정한다.
  4. mutex가 locked 상태라면 현재 스레드는 대기 큐에 추가되고, 스레드느 blocked 상태로 전환된다.

mutex의 단점

뮤텍스는 간단한 동작 원리로 동기화를 이뤄냈지만,
높은 우선순위의 스레드가 낮은 우선순위의 스레드에 의해 블로킹될 수 있는
우선순위 역전 문제와 교착 상태가 발생할 수 있다.
이러한 문제들은 다른 동기화 장치들에서도 발생할 수 있는 문제들로
이를 방지하기 위한 다양한 방법들이 존재한다.


Semaphore

세마포어라는 용어는 철도 및 해상에서 신호를 보내는 장치에서 유래되었다.
컴퓨터 공학에서 세마포어는 공유자원에 대한 접근이 가능한지 알 수 있는 장치로써 사용된다.

세마포어는 정수 값을 사용하여 현재 접근 가능한 스레드 수를 나타내는 변수이다.
이 변수는 두 가지 원자적 연산을 통해 조작되는데
세마포어의 값을 감소시키는 P(wait) 연산과,
세마포어의 값을 증가시키는 V(post) 연산이 있다.

semaphore의 동작방식

  • 접근 시

    1. 스레드는 P 연산을 통해 세마포어의 값을 감소시켜 공유 자원에 대한 접근을 시도한다.
    2. 세마포어 값이 0보다 크면 값을 1 감소시키고 자원에 접근한다.
    3. 세마포어 값이 0이면 스레드는 대기 상태로 전환된다.
  • 사용 후

    1. 스레드는 자원 사용을 마친 후 세마포어 값을 증가시킨다.
    2. 대기 중인 스레드가 있으면 해당 스레드를 깨워서 자원에 접근할 수 있게 한다.

semaphore의 종류

세마포어는 두 가지 종류로 나뉘는데
0과 1의 값을 가지는 Binary Semaphore
0 이상의 값을 가질 수 있는 Counting Semaphore이다.

binary semaphore는 뮤텍스와 유사한 동작을 하지만 뮤텍스와 달리 소유자의 개념이 없다.

semaphore의 한계

세마포어는 자원 접근의 수를 제어하는 데 유용하지만,
공유 자원에 대한 동시 쓰기 작업을 안전하게 수행하려면
뮤텍스와 같은 추가적인 동기화 메커니즘이 필요하다.

사실 나는 세마포어가 동기화 장치이기 때문에 당연히 모든 경쟁조건을 막아줄 것이라 생각했는데,
여러 스레드의 접근을 허용하면서 어떻게 이게 가능할까 했는데
알고보니 못하고 있었다..!!

그렇다면 세마포어는 왜 쓰는거지? 라는 의문이 들었는데
약간 다이어트 보조제처럼 "체중감소에 도움을 줄 수 있음" 같은 느낌이었다.


여기까지 멀티 스레딩에 대해 정리해보았고
동기화 장치에 monitor나 condition varaiable등이 더 있는데
다음에 궁금하면 또 정리해보겠다..

profile
안녕하세요 준갑습니성

0개의 댓글