멀티 스레드 / 비동기 병렬 처리

DD·2021년 1월 25일
8

프로그래밍 이론

목록 보기
10/12

멀티 스레드

하나의 프로세스를 두 개 이상의 스레드(실행단위)로 구분하여 자원을 공유하고, 자원의 생성 및 관리의 중복성을 최소화하여 수행 능력을 향상시키는 방식

구조

운영체제로부터 할당 받은 프로세스의 메모리를 내부 스레드들이 공유한다.

code, data, heap은 공유하고 스레드마다 각자의 stack 영역을 가진다.

스택만 분리하는 이유

  1. stack을 스레드끼리 공유한다면 선입후출의 특성을 가진 스택에 여러 스레드의 작업이 섞여 더 복잡해진다.
    때문에 원활한 실행 흐름을 위해 스택은 스레드별로 독립적으로 존재하게 된다

동작 방식

  1. 프로그램이 메모리에 적재되어 Process가 생성된다.

  2. Process는 필요한 독립된 작업의 수 만큼 스레드를 생성한다.

  3. cpu 코어 하나당 하나의 스레드를 동시에 실행할 수 있는데(하이퍼스레딩 적용 x일때), 스레드의 수가 더 많아지면 컨텍스트 스위치를 해야한다.

  4. 아주 짧은 시간 스레드a를 실행시키고 멈춘 후, 스레드 b를 실행시키고 멈춤, c를 실행시키고 멈춤... 다시 a를 ...
    이런식으로 필요한 스레드를 조금씩 빠르게 번갈아가며 실행, 정지한다.

  5. 멀티 프로세스도 동일한 원리로 작동하지만 프로세스와 달리 스레드는 '스택 영역을 제외하고 자원을 공유' 하고 있기 때문에 컨텍스트 스위치시 발생하는 비용이 적다.


장단점

장점

  • 시스템의 처리율이 향상된다.

  • 시스템 자원 소모가 감소된다.

  • 프로그램의 응답 시간이 단축된다.

  • 프로세스 간 통신 방법(IPC)에 비해 스레드 간 통신 방법이 더 간단하다. (캐시 메모리를 비울 필요도 없어서 더 빠르다)

단점

  • 멀티 스레드의 경우 미묘한 시간차이나 잘못된 변수를 공유함으로써 오류가 발생할 수 있다.
    ex) a 스레드에서 사용중인 변수나 자료구조에 b가 접근하여 엉뚱한 값을 읽어온다던가..
    (따라서 스레드 간 통신에서는 충돌 문제가 발생하지 않도록 동기화 문제를 해결해야한다)

  • 위 과정에서 병목현상이 발생하여 성능이 저하될 가능성이 있다.

  • 디버깅이 어렵다.

  • 단일 프로세스 시스템에서는 효과를 기대하기 어렵다.


비동기

개요

이하는 JavaScript 기준으로 설명한다.

JavaScript는 싱글 스레드이다. CPU 갯수와 상관없이 main thread에서만 작업을 실행한다.

이 문제를 해결하기 위해 Web worker는 여러개의 JavaScript 청크를 동시에 실행할 수 있도록 worker라 불리는 별도의 스레드로 보낼 수 있다.

따라서 시간이 오래 걸리는 처리는 worker를 사용해 처리하면 blocking 발생을 막을 수 있다.

다만 Web worker는 DOM에 접근할 수 없고, 단순한 숫자만 계산하는 단점이 있다.
또한, worker에서 실행되는 코드는 blocking 되지 않지만 동기적으로 실행되어 함수를 사용할 때 문제가 발생한다.
어떤 함수가 이전의 여러 프로세스의 결과를 return 받아야 한다면 그 결과를 제대로 받아올 수 없는 경우가 생긴다.

ex) A에서 서버로부터 이미지를 받아오고, B에서 그 이미지를 처리한다해도 A의 결과가 나오기 전에 B가 실행되면 에러가 발생한다.

이러한 문제를 해결하기 위해 이벤트루프를 사용해 비동기 처리를 한다.

Main thread: Task A                   Task B
    Promise:      |__async operation__|

Promise는 Task A가 서버에서 이미지를 가져오는 동안 Task B를 기다리게 할 수 있다.
이 과정은 다른 곳에서 처리가 되므로, 비동기 작업이 진행되는 동안 main thread가 blocking되지 않는다.

동작 방식

사실 JavaScript(ECMAScript)에 이벤트 루프, 즉 동시성, 비동기에 관한 언급은 없다고 할 수 있다. (ES6부터는 조금 달라졌지만..)

V8 같은 자바스크립트 엔진은 단일 호출 스택(call stack)을 사용하며, 요청이 들어오면 해당 요청을 순차적으로 호출 스택에 담아 처리할 뿐이다.

비동기 요청, 동시성처리는 브라우저, Node.js가 하는 것이다.

즉 자바스크립트로 비동기 처리를 하기 위한 이벤트 루프는 브라우저, Node.js마다 약간의 차이가 있다.

  1. 환경의 차이 (브라우저는 샌드박스 환경)
  2. 그로 인한 이벤트 루프 구현체 차이 (크롬의 libevent / 노드의 libuv)
  3. 발생하는 이벤트 차이 (노드는 파일 시스템, 네트워크 작업등을 추가로 한다)

브라우저 VS NODE에서 비동기 차이

환경

Browser : 샌드박스 환경이다.

샌드박스란?

보호된 영역 내에서 프로그램을 동작시키는 것으로, 외부 요인에 의해 악영향이 미치니는 것을 방지하는 보안 모델

이 모델에서 외부로부터 받은 프로그램을 보호된 영역, 즉 상자안에 가두고 나서 동작시킨다. 상자는 다른 파일이나 프로세스로부터는 격리되어 내부에서 외부를 조작하는 것은 금지되어 있다.

Node : 샌드박스 환경이 아니다. 그로인해 파일 시스템에 접근하거나, 네트워크 작업이 가능하다.

이벤트

둘 다 비동기 Event-driven 패턴을 사용한다.

Browser : 컨텍스트에서 이벤트는 웹페이지에서 사용자 상호작용 (클릭, 마우스 이동, 키보드 이벤트 등..) 이지만

Node : 컨텍스트에서는 비동기 서버 작업(File I/O 접근, Network I/O 등..)이다

이벤트 루프 구현체

다른 이벤트 루프 라이브러리를 사용하지만 이벤트 루프라는 프로그래밍 패턴의 유사점을 공유한다.

Browser : 크롬의 경우 libevent

Node : libuv

이벤트 루프란?

자바스크립트 엔진은 작업(task)을 기다리고, task가 들어오면 이를 실행하고 모든 task를 실행하면 다시 잠들고를 반복하는 끝없는 반복(endless loop)을 한다. 이를 이벤트 루프라고 한다.

1. 작업이 있다면 해당 작업을 실행한 후, 남은 작업 중 가장 오래된 작업부터 실행한다. (큐, 선입선출)
2. 엔진은 추가 작업이 생길 때까지 잠들어있다가 추가 작업이 생기면 1번 과정으로 돌아간다.

엔진은 대부분의 시간에 아무것도 하지 않고 1)스크립트, 2)핸들러, 3)이벤트를 활성화할 때 등에만 동작한다.

1) 외부 스크립트 < script src="..." > 를 불러올 때, 작업은 스크립트를 실행하는 것입니다.

2) 유저가 마우스를 움직일 때, 작업은 mousemove 이벤트를 실행하고 핸들러를 실행하는 것입니다.

3) 예약된 setTimeout 시간이 만료될 때, 작업은 setTimeout의 콜백 함수를 실행시키는 것입니다.

마이크로 vs 매크로 태스크

Macro-tasks(매크로 태스크) : 엔진이 뭔가 하고 있을 때 새로운 작업이 들어오면 미뤄진 작업들로 형성된 대기열을 "매크로테스크 큐" 라고 한다.
ex) 스크립트를 실행중에 마우스이벤트, setTimeout 설정 시간 만료 등..

Micro-tasks(마이크로 태스크) : 우리의 코드로 인해서만 생성된다. 매크로 태스크보다 더 높은 우선 순위를 가진다.
ex) promise

  1. 이벤트 루프는 마이크로 태스크 큐가 비어있는지 먼저 확인하고

  2. 마이크로 태스크 큐에 있는 콜백들을 실행한 후에

  3. 매크로 태스크 큐를 확인하고 실행한다.

  4. 태스크 큐의 콜백을 하나 실행한 후 1로 돌아간다.


참고

[OS]멀티스레딩에 대해서

[OS]프로세스(Process)와 스레드(Thread)의 차이/멀티 프로세스와 멀티 스레드의 개념 ,특징, 장단점

일반적인 비동기 프로그래밍 개념(MDN)

Browser VS Node Event Loop 차이

nodejs의 내부 동작 원리 (libuv, 이벤트루프, 워커쓰레드, 비동기)

자바스크립트와 이벤트 루프

profile
기억보단 기록을 / TIL 전용 => https://velog.io/@jjuny546

0개의 댓글