Process / Thread

✏️ Process

  • 운영체제 위에서 연속적으로 실행되고 있는 프로그램
    • 사진 뷰어 프로그램 / 음악 프로그램 등 각각이 프로세스
  • 메모리 위에서 서로 독립적으로 실행
    • 독립적으로 실행되기때문에 프로세스 하나에 문제가 생긴다고해서, 다른 프로세스에 영향이 가지 않음 (ex. 사진 뷰어 프로그램을 사용하다가 문제가 생기면 그 프로그램만 강제 종료 시킴)
  • 각각의 프로세스는 프로세스 마다 할당된 메모리나 데이터들이 지정되어 있음
  • Code : 프로그램을 위해 작성된 코드 (프로그램을 실행하기 위한 코드)
  • Stack : 프로세스 안에서 함수들이 어떤 순서로 실행되어야 하는지, 이 함수가 끝나면 어디로 되돌아가야하는지에 대한 정보가 담겨있음
  • Heap : Object 를 생성하거나 데이터를 만들때, 데이터가 저장되는 공간 (동적으로 할당된 데이터)
  • Data : 전역변수 , static 변수

✏️ Thread

  • 쓰레드는 한 프로세스 안에서 여러개가 동작할 수 있음
  • 각각 저마다 해야 되는 업무를 배정 받음
    • 작은 일꾼 단위
  • 쓰레드는 자기들만의 수행해야하는 함수의 호출을 기억해야하기 때문에 쓰레드마다 고유의 스택을 가지고있다.
    • 이 쓰레드는 결국은 한 프로그램을 위해서 일하는 일꾼이므로 Code, Heap, Data를 공통적으로 접근하여 업데이트가 가능하다
    • ex) 음악을 들으면서 사진을 편집할 수 있는 어플리케이션이라고 한다면 음악을 재생하는 쓰레드, 사진을 편집할 수 있는 쓰레드 혹은 서버로부터 음악을 받아와서 처리하는 쓰레드 등으로 나뉜다.
  • 쓰레드는 동시다발적으로 발생할 수 있기 때문에 프로세스가 조금 더 효율적으로 일할 수 있도록 도움을 준다. (만약 프로세스가 하나의 일밖에 하지 못한다면, 음악을 듣는 동안 사진 편집을 할 수 없다)
  • 쓰레드는 이 프로세스에 공통적으로 할당된 리소스에 동시다발적으로 접근할 수 있기때문에 효율적으로 프로그래밍을 동작시킬 수있지만 공통 자원의 업데이트가 일어나기때문에 쉽지 않다.

Java vs Javascript

자바(Java)언어는 언어 자체에서 멀티쓰레딩이 지원이 된다. 이 말은 쉽게 말해 프로그래밍을 짤 때, 사용자가 이 데이터를 보고 있는 동안 서버에서 데이터를 받아오는 것은 쓰레드A, 다른 일은 쓰레드B 와 같이 각각 지정해서 프로그래밍을 짤 수 있다.

하지만 javascript 는 Single Threaded Language 이다! 🙂

그래서 자바에서 하는 것처럼 이 일은 백그라운드 A 쓰레드에서 해야지, 이 일은 B 쓰레드에서 해야지 처럼 지정할 수 있는 방법이 전혀 없다!

그럼 현재 대부분의 웹 어플리케이션에서는 이 일을 하는동안 데이터도 받아오고, 꽤 많은 일을한다. 이건 어떻게 처리한 것일까 ? 🤔

이는 자바스크립트 언어 자체는 멀티쓰레딩을 할 수 있는 방법은 없지만, 이 자바스크립트가 동작하고 있는 브라우저 위에는, 즉 , 브라우저라는 프로그램 안에는 여러 가지의 쓰레드가 들어있다. 그래서 우리는 브라우저, 즉 , 웹 APIs 들을 이용하게 되면 멀티쓰레딩이 가능하다.

그리고 자바스크립트가 동작하는 런타임 환경(실행 환경)에서는 다양한 방식을 이용해서 조금 멀티쓰레딩 같은 효과를 얻을 수 있다.

또한 자바스크립트 런타임 환경에서는 이런 멀티쓰레딩 뿐만 아니라 이벤트 루프를 이용하여 조금 더 다양한 동작을 실행할 수 있다.


Javascript engine

내가 작성한 웹 어플리케이션이 브라우저위에 올라가는 순간, 이 자바스크립트 엔진이 작성한 소스코드를 한줄 한줄 해석하고 분석하고 실행한다.

자바스크립트 엔진에는 크게 Memory Heap 과 Call Stack 이 있다.

  • Memory Heap
    • 변수를 선언해서 오브젝트를 할당하거나 문자열이나 숫자를 할당하면 그 데이터들은 메모리 힙에 저장된다.
    • 구조적으로 정리된 자료구조가 아니라 자료가 여기저기 아무곳에나 저장됨
  • Call Stack
    • 함수를 실행하는 순서에 따라 차곡차곡 쌓아두는 영역
    • 함수가 실행되는 과정을 기억하기 위해 쓰이는 자료구조
    • LIFO (Last In First Out)
    • second() -> first() -> main()

JS callstack 테스트

'use strict';
function second(){
    console.log('hello');
    return; //first()로 돌아감
}
function first(){
    second();
    return; //main()으로 돌아감
}
function main(){
    first();
    return;
}
main();

위 코드에서, main()이 제일 먼저 호출되었기 때문에 main()이 콜스택에 제일 먼저 들어가게 된다. 그 다음 main() 내에서 호출된 first()가 콜스택으로 들어가게 되고, second()이 마지막으로 호출되었으므로 콜스택 제일 마지막에 쌓이게 된다.

second()에서 hello를 출력한 뒤, return을 만났으므로 어디로 돌아가야할지 확인을 위해 엔진은 콜 스택을 확인한다. (콜스택에는 어디로 돌아와야되는지에 대한 정보도 포함됨) 콜 스택을 확인하여 마지막으로 실행된 코드라인의 다음 라인인 first()의 return => main()의 return 순으로 진행되며 콜스택이 비어지게 된다.

모든 프로세스와 쓰레드 안에는 각각 저마다의 콜 스택이 들어가있다. 일을 수행할 때 어디서 왔고 어디로 다시 돌아가야하는지에 대한 정보를 기억해야하기 때문이다.

'use strict';
function endless(){
    endless();
}
endless();

그럼 위와 같이 잘못된 재귀함수 코드는 어떻게 될까 ?

콜스택에 그림과 같이 endless() 함수가 계속해서 쌓일것이다. 결국 브라우저는 에러 메세지를 뱉게 된다 🙂 ..

Maximum call stack size exceeded error