Process & Thread


프로세스와 스레드

프로세스

일반적으로 CPU에 의해 처리되는 사용자 프로그램, 시스템 프로그램 즉 실행중인 프로그램을 의미하며, 작업(Job) 태스크(Task)라고도 한다.

즉 프로세스는 운영체제로부터 자원을 할당받는 작업이 단위이다.

프로그램에서 프로세스로 메모리에 올라올 때 운영체제로부터 프로세스를 운영하기 위해
필요한 주소 공간, 메모리(Text, Data, Heap, Stack 공간)을 부여 받는다.

스레드

한 프로세스 내에서 동작되는 여러 실행의 흐름으로,
프로세스 내의 주소 공간이나 자원들을 같은 프로세스 내의 스레드끼리 공유하며 실행된다.

스레드는 프로세스 내에서 각각의 스택 공간을 제외한 나머지 공간과 시스템 자원을 공유한다.
프로세스를 이용하여 동시에 처리하던 일을 스레드로 구현할 경우 메모리 공간과 시스템 자원 소모도 현격히 줄어들게 된다.

하나의 프로세스에서 여러 스레드가 존재하게 되니 스레드 간의 통신이 필요한 경우 별도의 자원을 이용하는 것이 아니라
메모리 공간을 공유하므로 데이터 세그먼트, 즉 전역 변수를 이용하여 구현한다.

그런데 전역 변수를 여러 스레드가 함께 사용하게 되면 충돌이 발생하게 되서 동기화 문제를 해결해야 한다.

스레드 사용 이유

하나의 프로세스에 1 ~ N 개의 스레드가 생성된다.
스레드는 가벼운 프로세스라고도 불리고 프로세스에서 만들어 사용하고 있는 메모리를 공유한다.

멀티 프로세스로 실행되는 작업을 멀티 스레드로 실행하게 되면 프로세스를 생성하여 자원을 할당하는 과정도 줄어들 뿐더러
프로세스를 컨텍스트 스위칭(Context Switching)하는 것 보다 오버헤드를 더 줄일 수 있게 된다.

뿐만 아니라 프로세스간의 통신 비용보다 하나의 프로세스 내에서 여러 스레드간의 통신 비용이 훨씬 적으므로
작업들 간의 통신 부담을 줄일 수 있게 된다.

프로세스와 스레드의 차이

프로세스와 스레드의 근본적인 차이는 프로세스는 운영체제로부터 독립된 시간, 공간 자원을 할당 받아 실행된다는 점이고,
스레드는 한 프로세스 내에서 많은 자원을 공유하면서 병렬적으로(Concurrently) 실행된다는 것이다.
다른 차이는 모두 이 근본적인 차이에서 비롯된다.

프로세스 / 스레드


멀티 스레드

멀티 스레드는 하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행하는 것을 의미한다.
멀티 프로세스(multi process)는 여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행하는 것을 의미합니다.

멀티 스레드와 멀티 프로세스 모두 여러 흐름을 동시에 수행한다는 공통점이 있다.
그러나 멀티 프로세스는 각 프로세스가 독립적인 메모리를 가지고 별도로 실행되지만,
멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유한다는 점이 다릅니다.

컴퓨터에서 동시에 처리할 수 있는 최대 작업 수는 CPU의 코어(core) 수와 같습니다.
만약 CPU의 코어 수보다 더 많은 스레드가 실행되면,
각 코어가 정해진 시간 동안 여러 작업을 번갈아가며 수행하게 됩니다.

이때 각 스레드가 서로 교체될 때 스레드 간의 문맥 교환(context switching)이라는 것이 발생합니다.
문맥 교환이란 현재까지의 작업 상태나 다음 작업에 필요한 각종 데이터를 저장하고 읽어오는 작업을 가리킵니다.

이러한 문맥 교환에 걸리는 시간이 커지면 커질수록, 멀티 스레딩의 효율은 저하됩니다.
오히려 많은 양의 단순한 계산은 싱글 스레드로 동작하는 것이 더 효율적일 수 있습니다.

따라서 많은 수의 스레드를 실행하는 것이 언제나 좋은 성능을 보이는 것은 아니라는 점을 유의해야 합니다.


동기와 비동기

동기 (synchronous)

동시에 일어난다는 뜻으로 요청과 그 결과가 동시에 일어난다는 것이다.
시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 한다.

요청한 결과가 한 자리에서 동시에 일어난다. = A노드와 B노드 사이의 작업 처리 단위(transaction)을 동시에 맞춘다.

예를들어,
어떤 객체 또는 함수 내부에서 다른 함수를 호출했을 때
이 함수의 결과를 호출한 쪽에서 처리하면 동기입니다.

일반적으로 싱글 스레드를 사용한 처리방식이 이에 해당한다.

비동기 (Asynchronous)

동시에 일어나지 않는다를 의미한다. 요청과 결과가 동시에 일어나지 않는다.

요청한 그 자리에서 결과가 주어지지 않음
= 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 된다.

예를들어,
어떤 객체 또는 함수 내부에서 다른 함수를 호출했을 때
이 함수의 결과를 호출한 쪽에서 처리하지 않으면 비동기입니다.

동기와 비동기의 차이

동기방식은 매우 설계가 간단하고 직관적이지만
결과가 주어질 때까지 아무것도 못하고 대기해야 한다는 단점이 있다.

비동기방식은 좀 더 복잡하지만 결과가 주어지는 시간이 길어져도 그 시간 동안 다른 작업을 할 수 있어서 좀 더 효율적으로 자원을 사용할 수 있는 장점이 있다.

작업 요청한 스레드가 결과를 받을 때까지 멈추느냐 아니냐의 차이가 있다.
단, 싱글 스레드의 경우 작업을 요청한 스레드에서 처리해야 하므로 스레드가 멈추지는 않는다.

블로킹 (Blocking)

네트워크 통신에서 요청이 발생하고 완료될 때까지 모든 일을 중단한 상태로
대기해야 하는 것을 블로킹 방식이라고 한다.

자신의 수행결과가 끝날 때까지 제어권을 갖고 있는 것을 의미한다.

작업이 끝날 때까지 대기했다가 결과를 반환 받아서 처리한다.

논블로킹 (Non-blocking)

자신이 호출되었을 때 제어권을 바로 자신을 호출한 쪽으로 넘겨
자신을 호출한 쪽에서 다른 일을 할 수 있도록 하는 것이다.

예를들어, setTimeout(hyerin, 300) 함수를 호출했을 떄
3초 후에 hyerin() 함수가 호출되는데 제어권을 반납했기 떄문에
3초 동안 다른 일을 수행할 수 있다.

네트워크 통신이 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있어
경우에 따라 효율이나 반응속도가 뛰어나다.
반면 설계가 복잡해진다는 단점이 있다.


synchronized & volatile

synchronized

하나의 객체에 여러 객체가 동시에 접근하여 처리하는 상황이 발생할 때 사용한다.

JVM 내에서 synchronization은 어떻게 동작할까?

자바의 HotSpot VM은 ‘자바 모니터’를 제공함으로써 스레드들이 ‘상호 배제 프로토콜’에 참여할 수 있도록 돕는다.

자바 모니터는 잠긴 상태(lock)나 풀림(unlocked) 중 하나이며, 동일한 모니터에 진입한 여러 스레드들 중에서
한 시점에서 단 하나의 스레드만 모니터를 가질 수 있다.

모니터를 가진 스레드만 모니터에 의해서 보호되는 영역에 들어가서 작업을 할 수 있다.
여기서 보호된 영역이란 앞서 설명한 synchronized 로 감싸진 블록들을 의미한다.

모니터를 보유한 스레드가 보호 영역에서의 작업을 마치면, 모니터는 다른 대기중인 스레드에게 넘어간다.

volatile

volatile 키워드는 java 변수를 메인 메모리에 저장하겠다는 것을 명시하는 것이다.
매번 변수의 값을 read할 때마다 CPU cache에 저장한 값이 아닌 메인 메모리에서 읽는 것이다.
또한 write 할 때도 메인 메모리에 작성한다.

Q. 왜 volatile 키워드가 필요할까?

volatile 변수를 사용하고 있지 않은 멀티 스레드 어플리케이션에서는 Tack를 수행하는 동안
성능 향상을 위해 메인 메모리에서 읽은 변수 값을 CPU cache에 저장하게 된다.

만약에 멀티 스레드 환경이라면 스레드가 변수 값을 읽어올 때
각각의 CPU cache에 저장된 값이 다르기 때문에 변수 값 불일치 문제가 발생하게 된다.

volatile 키워드를 추가하게 되면 메인 메모리에 저장하고 읽어오기 때문에
변수 값 불일치 문제를 해결할 수 있다.

Q. 언제 volatile이 적합할까?

멀티 스레드 환경에서 하나의 스레드만이 read&write하고 나머지 스레드가 read하는 상황에서
가장 최신의 값을 보장해야 할 때 volatile를 사용해야 한다.

여러 스레드가 write 하는 상황에서는
synchronized 를 통해 변수 read&write의 원자성을 보장해야 한다.

Q. volatile는 성능에 어떤 영향이 있을까?

volatile는 변수의 read와 write를 메인 메모리에서 진행하게 된다.
CPU cache보다 메인 메모리가 비용이 더 크기 때문에
변수 값 일치를 보장해야 하는 경우에만 volatile를 사용하는 것이 좋다.