멀티테스킹
OS에서는 멀티 테스킹을 지원합니다. Linux, Windows 등이 모두 이에 해당되죠.
즉, 한 컴퓨터에서 인터넷 검색, 노래 듣기, 게임을 동시에 할 수 있는 것이죠
그럼 어떻게 많은 작업을 동시에 처리할 수 있을까요? cpu 성능이 좋지 않으면 동시에 처리하는 기능이 떨어지는 걸까요?
게임과 같이 처리량이 많은 업무를 할때, 다른 업무를 많이 처리하게 한다면 동시에 처리되지 않고 버벅이거나 게임할 때 자막이 잘 나오지 않는 것 그런것들 모두 이것과 관련이 있습니다.
실제로 동시에 처리될 수 있는 프로세스의 개수는 CPU 코어의 개수와 동일합니다.
이보다 많은 개수의 프로세스가 존재하기 때문에 모두 함께 동시에 처리할 수는 없습니다.
각 코어들은 아주 짧은 시간에 여러 프로세스를 번갈아가며 처리하는 방식을 통해 동시에 동작하는 것처럼 보이게 할 뿐이죠
이런 방식처럼 멀티스레딩이란 하나의 프로세스 안에 여러개의 스레드가 동시에 작업을 수행하는 것을 의미합니다.
프로세스와 스레드의 정의
프로세스와 스레드의 정의를 먼저 볼까요?
프로세스 : 운영체제로부터 자원을 할당받은 작업의 단위
스레드 : 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위
이 말만 봤을 때는 이해가 가지 않을 수있습니다.
프로세스의 구조를 조금 살펴보면
저장 공간과 커널 관련 자료 구조로 구성되어 있습니다.
저장공간은 code,data, stack으로 구성되어 있고, 커널 관련 자료 구조는 PCB, Kernel Stack으로 구성되어 있습니다.
이때 PCB의 pointer는 큐(Queue)의 형태로 구성되는 스케줄러에 따라 다음에 처리해야할 프로세스의 주소값을 가리킵니다.
따라서 아주 작은 단위로 여러개의 프로세스가 동작할때, pointer와 우선순위에 따라 순서가 정해지고 그 프로세스 내부에 스레드를 통해 다양한 업무를 동시에 하는 것처럼 처리한다는 뜻입니다.
프로그램 vs 프로세스
컴퓨터에서 프로그램을 많이 봤을 것입니다. chrome, 카카오톡 등 컴퓨터에서 실행 할 수 있는 파일을 이릅니다.
이것들은 컴퓨터 안에 저장되어있을 때, 파일을 실행시키지 않은 상태이기 때문에 Static Program을 줄여서 Program이라고 부릅니다.
이런 프로그램들은 개발자들이 만든 코드로 구성되어있습니다.
그리고 코드로 구성된 프로그램을 실행시켜 동적으로 변화시켰을때 프로그램이 돌아가고 있는 상태를 프로세스라고 합니다.
`ctrl + alt + del` 단축키를 눌려 작업 관리자를 확인해보면
Google Chrome(42)와 같이 한개의 프로그램도 여러개의 프로세스로 동작하는 것을 볼 수 있습니다.
하지만 42개를 동시에 동작하려면 42개의 CPU core가 필요하다는 뜻일까요?
그럼 너무 비용도 비싸고 비효율적입니다. 이것을 효율적으로 사용하기 위해 OS는 작업 단위를 아주 작은 시간 단위로 쪼개서 CPU 별로 할당합니다.
즉 메모리 공간을 할당해서 프로그램 코드를 실행시켜 서비스를 사용할 수 있도록 만들어 주는것입니다.
프로세스 vs 스레드
프로세스의 한계
이런 다용도로 사용되는 프로세스는 한계가 있습니다. 오늘날 컴퓨터는 파일을 다운 받으며 다른 일을 하는 멀티 작업이 당연하지만, 과거에는 파일을 다운받으면 실행 시작부터 끝까지 하나의 프로세스로 처리되기에 다운될때까지 하루종일 기다려야 했습니다. 그렇다고 동일한 프로그램을 여러개의 프로세스로 만들면 그만큼 메모리를 차지하고 CPU의 할당받는 자원이 중복됩니다. 스레드는 이런 문제를 극복하기 위한 용도로 생겼습니다.
일반적으로 하나의 프로그램은 하나 이상의 프로세스를 들고 있으며, 하나의 프로세스는 반드시 하나 이상의 스레드를 가집니다.
그리고 스레드는 프로세스의 자원을 공유하며 스레드 실행 흐름의 일부가 되기 때문에 동시 작업이 가능합니다.
각각의 Stack은 고유하지만 나머지 Code, Data, Heap의 저장공간은 공유를 통해 자원을 사용합니다.
Java에서의 스레드
Java에서 스레드는 아래와 같이 동작합니다.
스레드는 이처럼 생성될 때마다 스택이 새롭게 생성될 뿐 프로세스 내에서 힙,데이터를 공유하기 때문에 프로세스 콘텍스트 스위칭(Context Switching)에 비해 오버헤드를 절감합니다.
* 콘텍스트 스위칭 : 스케줄러가 기존 실행 프로세스를 우선순위 때문에 미루고 새 프로세스로 교체해야 할 때 프로세스 상태 값을 교체하는 작업
스레드를 코드로써 구현하는 방법은 2가지가 있습니다.
첫 번째, Runnable 인터페이스 구현
두 번째, Thread 클래스 상속
둘다 run() 메소드를 오버라이딩 하는 방식입니다.
class ThreadEx1 {
public static void main(String args[]) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
t1.start(); // thread-0 thread 사용
t2.start(); // thread-1 thread 사용
t1.run(); // main thread 사용
}
}
class ThreadEx1_1 extends Thread {
public void run() {
for(int i=0; i < 5; i++) {
System.out.println(getName()); // 조상인 Thread의 getName()을 호출
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for(int i=0; i < 5; i++) {
// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
System.out.println(Thread.currentThread().getName());
}
}
}
Thread Class를 상속받는 것은 자손 클래스의 인스턴스를 생성해서 바로 사용합니다.
Runnable Interface로 구현한 것은 Runnable 인스턴스를 생성하고, Thread 클래스의 생성자에 Runnable 인터페이스에서 구현한 인스턴스를 매겨변수로 주고 Runnable에서 구현한 것을 참조합니다.
이후, start() 메소드를 통해 호출하면 실행하게 됩니다.
이때 실행 대기 상태가 되고, 자신의 차례가 되었을 때 실행됩니다.
start()와 run()의 차이는 start()안에는 run() 메소드 밖에 없고, 새로운 스레드로 실행한다는 것에 의의를 가지고 있습니다.
'기술 스텍 > Java' 카테고리의 다른 글
[Java] Interned String (1) | 2024.04.05 |
---|---|
[Java] Record란? (0) | 2024.04.05 |
자바 버전에 따른 차이 (0) | 2024.02.19 |
Casting 이란? (0) | 2024.02.17 |
Object 클래스란? (0) | 2024.02.07 |