1. 프로세스와 스레드 소개
- 멀티태스킹 vs 멀티프로세싱
- 멀티태스킹
- 단일 CPU(단일 CPU 코어)가 여러 작업을 동시에 수행하는 것처럼 보이게 하는 것을 의미한다.
- 소프트웨어 기반으로 CPU 시간을 분할하여 각 작업에 할당한다.
- 예: 현대 운영 체제에서 여러 애플리케이션이 동시에 실행되는 환경
- 멀티프로세싱
- 여러 CPU(여러 CPU 코어)를 사용하여 동시에 여러 작업을 수행하는 것을 의미한다.
- 하드웨어 기반으로 성능을 향상시킨다.
- 예: 다중 코어 프로세서를 사용하는 현대 컴퓨터 시스템
- 코드, 힙, 기타(데이터섹션), 스택에는 각각 무엇이 있을까?
- 코드 섹션: 실행할 프로그램의 코드가 저장되는 부분
- 데이터 섹션: 전역 변수 및 정적 변수가 저장되는 부분(그림에서 기타에 포함)
- 힙 (Heap): 동적으로 할당되는 메모리 영역
- 스택 (Stack): 메서드(함수) 호출 시 생성되는 지역 변수와 반환 주소가 저장되는 영역(스레드에 포함)
- 컨텍스트 스위칭에 대해 설명하시오.
- 컨텍스트는 현재 작업하는 문맥을 뜻한다.
- 현재 작업하는 문맥이 변하기 때문에 컨텍스트 스위칭이다.
- 컨텍스트 스위칭 과정에서 이전에 실행 중인 값을 메모리에 잠깐 저장하고, 이후에 다시 실행하는 시점에 저장한 값을 CPU에 다시 불러와야 한다.
- 결과적으로 컨텍스트 스위칭 과정에는 약간의 비용이 발생한다.
- CPU 바운드 작업 vs I/O 바운드 작업에 대해 설명하시오
- CPU-바운드 작업 (CPU-bound tasks)
- CPU의 연산 능력을 많이 요구하는 작업을 의미한다.
- 이러한 작업은 주로 계산, 데이터 처리, 알고리즘 실행 등 CPU의 처리 속도가 작업 완료 시간을 결정하는 경우다.
- 예시: 복잡한 수학 연산, 데이터 분석, 비디오 인코딩, 과학적 시뮬레이션 등
- I/O-바운드 작업 (I/O-bound tasks)
- 디스크, 네트워크, 파일 시스템 등과 같은 입출력(I/O) 작업을 많이 요구하는 작업을 의미한다.
- 이러한 작업은 I/O 작업이 완료될 때까지 대기 시간이 많이 발생하며, CPU는 상대적으로 유휴(대기) 상태 에 있는 경우가 많다.
- 쉽게 이야기해서 스레드가 CPU를 사용하지 않고 I/O 작업이 완료될 때 까지 대기한 다.
- 예시: 데이터베이스 쿼리 처리, 파일 읽기/쓰기, 네트워크 통신, 사용자 입력 처리 등
- 웹 애플리케이션 서버는 어떤 바운드 작업이 많을까?
- 분야마다 다르겠지만, 실무에서는 CPU-바운드 작업 보다는 I/O-바운드 작업이 많다.
- 예를 들어서 백엔드 개발자의 경우 주로 웹 애플리케이션 서버를 개발하는데, 스레드가 1 ~ 10000까지 더하는 CPU의 연산이 필요한 작업보다는, 대부분 사용자의 입력을 기다리거나, 데이터베이스를 호출하고 그 결과를 기다리는 등, 기다 리는 일이 많다.
- 쉽게 이야기해서 스레드가 CPU를 많이 사용하지 않는 I/O-바운드 작업이 많다는 뜻이다.
- CPU 바운드 작업, I/O 바운드 작업에는 어느 정도의 스레드를 할당하는 것이 좋은가?
- CPU-바운드 작업: CPU 코어 수 + 1개
- CPU를 거의 100% 사용하는 작업이므로 스레드를 CPU 숫자에 최적화
- I/O-바운드 작업: CPU 코어 수 보다 많은 스레드를 생성, CPU를 최대한 사용할 수 있는 숫자까지 스레드 생성
- CPU를 많이 사용하지 않으므로 성능 테스트를 통해 CPU를 최대한 활용하는 숫자까지 스레드 생성
- 단 너무 많은 스레드를 생성하면 컨텍스트 스위칭 비용도 함께 증가 - 적절한 성능 테스트 필요
2. 스레드 생성과 실행
- Thread 함수의 start()와 run()을 실행하는 것의 차이점은?
- start() 함수 실행시 main 외에 별도의 스레드와 스택이 생성되어 멀티 스레딩 환경이 구성됨.
- run() 함수 실행시 main 스택 위에 스택이 쌓이게 되어 멀티스레딩 환경이 구성되지 않음.
- 사용자 스레드와 데몬 스레드의 차이점은?
- 사용자 스레드(non-daemon 스레드)
- 프로그램의 주요 작업을 수행한다.
- 작업이 완료될 때까지 실행된다.
- 모든 user 스레드가 종료되면 JVM도 종료된다.
- 데몬 스레드
- 백그라운드에서 보조적인 작업을 수행한다.
- 모든 user 스레드가 종료되면 데몬 스레드는 자동으로 종료된다
- Thread 함수만 사용하는 것과 Runnable 함수도 사용하는 것의 차이는 어디서 발생하는가?
- 실행 결과는 기존과 같다.
- 차이가 있다면, 스레드와 해당 스레드가 실행할 작업이 서로 분리되어 있다는 점이다.
HelloRunnable runnable = new HelloRunnable();
Thread thread = new Thread(runnable);
thread.start();
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": run()");
}
}
- Thread 함수만 사용하는 것과 Runnable 함수도 사용하는 것의 장단점은?
- Thread 클래스 상속 방식
- 장점
- 간단한 구현: Thread 클래스를 상속받아 run() 메서드만 재정의하면 된다.
- 단점
- 상속의 제한: 자바는 단일 상속만을 허용하므로 이미 다른 클래스를 상속받고 있는 경우 Thread 클래스를 상속 받을 수 없다.
- 유연성 부족: 인터페이스를 사용하는 방법에 비해 유연성이 떨어진다.
- Runnable 인터페이스를 구현 방식
- 장점
- 상속의 자유로움: Runnable 인터페이스 방식은 다른 클래스를 상속받아도 문제없이 구현할 수 있다.
- 코드의 분리: 스레드와 실행할 작업을 분리하여 코드의 가독성을 높일 수 있다.
- 여러 스레드가 동일한 Runnable 객체를 공유할 수 있어 자원 관리를 효율적으로 할 수 있다.
- 단점
- 코드가 약간 복잡해질 수 있다.
- Runnable 객체를 생성하고 이를 Thread 에 전달하는 과정이 추가된다.
- 정리
- Runnable 인터페이스를 구현하는 방식을 사용
- 스레드와 실행할 작업을 명확히 분리하고, 인터페이 스를 사용하므로 Thread 클래스를 직접 상속하는 방식보다 더 유연하고 유지보수 하기 쉬운 코드를 만들 수 있다