1. 스레드

 

1.1 실행 중인 프로세스의 스레드 확인

    1. 윈도우 환경에서, Ctrl + Shift + ESC 키를 눌러 Task Manager(작업 관리자)를 실행한다.
    2. [ Details ]  탭에서 컬럼명(Name, Status...)을 오른쪽 클릭하면 보여질 추가 정보를 선택할 수 있다.
    3. Select Columns 에서 Threads 를 체크한다.
    4. 메모장 프로그램은, 실행(프로세스)중에 15개의 스레드가 작업하고 있다.
      (스레드의 이해 없이 프로그래밍은 불가능하다.)

Ctrl + Shift + ESC > [Details] > [Name] > right click > select columns > check threads

 

 

1.2 스레드란?

 

1.2.1 Process(프로세스)가 "실행" 단위라면, Thread(스레드)는 "실행 흐름" 단위다.

 

하나의 프로그램은 하나 이상의 프로세스를 가지고 있고, 하나의 프로세스는 반드시 하나 이상의 스레드를 갖는다.

프로그램 실행 = 프로세스 1개 이상 = 스레드 1개 이상

즉, 프로그램이 실행되면, 프로세스를 생성하고 기본적으로 하나의 main 스레드가 생성되게 된다.

 

1.2.2 소스코드의 실행 흐름은 순자척이다.

 

1.2.3 코드 블럭의 특징

  • 함수
    함수의 매개변수, 반환 주소값, 지역 변수 등이 저장
  • 지역변수, 매개변수 같은 컴파일 타임에 크기가 결정되는 메모리를 올리는 공간이다.
    ≒ 메모리 할당에 필요한 크기가 고정적이다.

 

1.2.4 예제

 

아래 소스코드를 빌드하면, 실행가능한 프로그램 형태로 저장된다.

저장된 프로그램이 실행 되면, 메모리에 적제되고, 운영체제가 관리하는 단위인 프로세스 가 된다.

그리고 스레드 라는 메인 함수의 실행 흐름을 갖는다.

즉, 프로세스(실행)는 최소 1개(main)의 스레드라는 실행 흐름을 갖는다.

 

멀티 스레드란, main 스레드라는 실행 흐름 이외에 별도의 "실행 흐름"이 동시에 동작하도록 하는 것을 의미한다.

여기에서 스레드는, 하나의 코어에서 동작하는 것을 기본 전제로 한다.



 

 

2. 스레드는 왜 등장했을까?

 

학습 방향

스레드의 등장 배경과 필요성, 사용법을 이해한다.

 

등장 배경 1, 등장 배경 2

 

 

2.1 초기 컴퓨터 

어떤 프로그램을 실행 중일 때, 실행 중인 작업이 끝나야 다른 작업을 수행할 수 있는 블로킹(Blocking) 방식이었다.

프로그램 실행 도중 I/O(입출력) 요청이 있으면 CPU는 연산을 멈추고 대기해야 했다.

 

I/O(입출력)

파일 읽기⋅쓰기, 데이터 베이스 읽기⋅쓰기, 네트워킹 보내기⋅결과 받기 등 동작

CPU와 메모리의 직접적 연산 처리와 거리가 있고, 처리가 느리다. 

 

 

2.2 블로킹 / 논블로킹

 

2.2.1 블로킹(Blocking)

처리가 느린 I/O 작업을 스레드가 기다리는 방식이다.

Main Thread는 I/O 다른 작업을 할 수 없다.

 

 

2.2.2 논블로킹(Non-Blocking)

처리가 느린 I/O 작업을 기다리지 않는 방식이다.

I/O를 시작하자마자 바로 함수를 리턴하여 Main Thread는 I/O 응답을 기다리지 않고 다른 작업을 할 수 있다.

 

 

2.3 동기 / 비동기

동기와 비동기는 스레드 처리 후의 작업, callback,을 어떤 스레드에서 처리하는지의 문제다.

정확히는 호출한 스레드에서 처리하는가, 다른 스레드에서 처리하는가의 차이다.

 

클라우드 및 데이터에서 동기화는 "같은 데이터"를 맞추는 것이다.

스레드에서 동기화는 "같은 실행 흐름"을 맞추는 것이다.

 

2.3.1 동기(Synchronous) + 블로킹

동기 방식은, 호출한 스레드에서 콜백을 작업을 실행한다.

블로킹 방식이므로, 'I/O를 기다렸다가 완료됐다고 응답을 받으면 콜백을 실행한다' 이다.

 

2.3.2 비동기(Asynchronous) + 논블로킹

비동기 방식에선 호출 스레드에서 콜백을 실행하지 않아도 된다.

논블로킹 작업을 할 때 Thread1이 I/O 요청 후에 받은 작업(Task2)이 너무 길어져서 당장 콜백을 처리할 수 없는 상황이라면 여유있는 스레드(Thread2)가 콜백을 실행한다.

운이 좋아서 블로킹-동기 방식과 동일하게 동작할수도 있으나 Task2가 언제끝날지 보장할 수 없다.

 

 

2.3.3 동기(Synchronous) + 논블로킹

동기 방식은, 호출한 스레드에서 콜백을 작업을 실행한다.

논블로킹 방식이므로,  Thread1은 I/O 응답를 기다리지 않는다. 하지만, 동기 방식이기 때문에 I/O를 시작한 스레드가 callback을 실행해야하므로 Task2가 길어진다면 callback은 큐에서 대기해야한다. 

지연이 발생하고. 또한 Thread2는 작업이 가능함에도 불구하고 아무 작업도 진행되지 않는다.

 

2.3.4 비동기(Asynchronous) + 블로킹

호출 스레드가 I/O 완료를 기다리는데, 콜백은 다른 곳에서 실행할 수도 있는 방식이다.

I/O가 끝나자마자 반응할 수는 있지만 I/O가 진행되는동안 스레드가 두개나 놀고 있어 비효율적다.

따라서 동기- 논블로킹, 비동기- 블로킹 는 잘 사용되지 않는다.

 

 

멀티 스레드 등장 배경

초기 컴퓨터는 하나의 스레드를 이용하여 한 번에 한 작업만 수행하는 싱글 스레드(Single thread)기반의 동기(Synchronous) + 블로킹 방식 이었다.

 

한 번에 2가지 이상의 일을 동시에 처리하는 병렬성 프로그래밍 방식이 1950년대 (IBM 701, IBM 704 ...) 등장했다.

순차적인 제어의 흐름을 의미하는 개념은 1965년 버클리 시분할 시스템에서 처음 쓰였지만, 이때에는 스레드라는 이름 대신에 프로세스라고 불렀다. 

 

단일 프로세서(single processor)는 한 번에 하나의 작업만 처리 할 수 있기에, 동시에 처리하는 구조는 기존 실행중인 작업을 잠시 멈추고 다른 일을 처리 하는 동작을 빠르게 반복 수행하는 것이다.

여러 작업이 동시에 실행되는 것처럼 보이지만, 커널 내부에서는 운영체제가 CPU 사용권을 적절히 분배하는 작업이 반복되고 있다.

 

초기 병렬 프로그래밍은 "멀티 프로세스" 기반으로 구현되었다.

"멀티 프로세스" 는 프로그램을 여러개 실행시키는 것과 동일하기에 하드웨어 리소스의 부담이 커서, 프로세스에 비해 상대적으로 효율적인 실행 단위인 일종의 '경량화 된(가벼워진) 프로세스'인 Thread(스레드)가 도입되었다.

 

 

프로세스 / 스레드

좌-멀티 프로세스, 우-멀티 스레드

 

 

장점

  • 멀티 프로세스 방식에 비해 상대적으로 멀티 스레드 방식이 자원 공유가 쉽다.
  • 프로세스간에 데이터를 교환 할 때 IPC(Inter Process Communication)을 이용해야 하지만, 쓰레드는 코드 내의 변수를 같은 데이터 교환에 특별한 기법이 필요하지 않다
  • 레드를 사용하면 이미 프로세스에 이미 할당된 메모리와 자원을 그대로 사용한다.
    (멀티 프로세스는 프로세스를 띄우기 위해 메모리와 자원을 할당하는 작업을 진행해야 한다)

 

 

단점

  • 여러 스레드가 존재한다면, 하나의 스레드에 문제 발생 시 다른 스레드에 영향을 줄 수 있다.
    (멀티 프로세스는 해당 프로세스만 죽는다)
  • 멀티 쓰레드 구조의 소프트웨어는 구현하기가 까다롭다.
    (테스트가 어렵고 디버깅 또한 쉽지 않다)
  • 쓰레드가 CPU를 사용하기 위해서는 작업간 전환 (Context Switching) 을 해야 한다. 
    (자주 작업 간 전환을 하기 되면 성능이 저하된다)

 

 

스레드 어원 가설 1
사전적으로 스레드는 각 부분을 연결하는 추론 또는 생각의 순차적 흐름 (아이디어 또는 이벤트)이라는 의미를 추상화한 표현이다
e.g) lost the thread of the story, 이야기의 논리적 연결 흐름을 잃어버리다

 

 

스레드 어원 가설 2

실(thread) 은 프로세스가 얇은 회로에서 전기 상태로 실행되기 때문에 와이어(wire)라고 부르기에는 상대적으로 얇은 표현인 실(thread)라고 부르기 시작했다.

 

 

리누스 토르발스(리눅스와 깃 창시자) 의견

프로세스와 스레드는 그냥, "실행의 문맥"일 뿐이다. (전체 문답 링크)

 

 

스레드 방식의 한계

운영체제가 스레드 간에 효율적인 *작업 전환(context switching)을 스케쥴링하더라도 *오버헤드 자체는 사라지지 않는다.

스레드의 생성, 삭제에서 발생하는 오버헤드 역시 마찬가지다.  

스레드가 작업을 수행하지 않더라도 존재 자체만으로 여전히 리소스를 소비하고 오버헤드를 생성한다.

 

*오버헤드: 추가적으로 시간, 메모리, 자원이 사용되는 현상

 

 

컨텍스트 스위칭

컨텍스트 스위칭(Context Switching) 에서 오버헤드가 발생하는 이유는, 둘 이상의 프로세스들이 CPU의 할당시간을 매우 작은 크기로 쪼개서 서로 나누는 과정(시분할 시스템, 멀티 태스킹)에서 프로그램의 실행을 위해서는 해당 프로세스의 정보가 메인 메모리에 올라와야 한다. (폰노이만 구조)

A 프로세스의 뒤를 이어서 B 프로세스를 실행시키려면 A 프로세스 관련 데이터를 메인 메모리에서 내리고 하드디스크로 이동시킨 뒤, B 프로세스 관련 데이터를 메인 메모리로 이동시켜야 한다. 바로 이것이 컨텍스트 스위칭이다. 

 

 

C# Thread 의 발전

  • Thread
  • ThreadPool
  • Task
  • await, async

 

링크