멀티 스레드란?
멀티 스레드란 한 응용 프로그램에 여러 스레드를 갖고 있는 것을 말한다. 스레드는 CPU가 처리하는 작업 단위를 말하며, 따라서 멀티 스레드는 CPU가 주어진 시간 동안 스레드를 전환하며 실행하는 것을 말한다.
만약, CPU의 코어가 여러 개라면 한 응용 프로그램 내에 코드의 다른 부분을 여러 개의 CPU 코어에서 실행할 수 있게 된다. 이를 그림으로 표현하면 아래와 같다.
왜 멀티스레드를 사용할까?
1. 단일 CPU일 때 CPU의 효율을 극대화할 수 있다.
CPU의 작업은 매우 비싼 작업이기 때문에 CPU를 잠시라도 쉬게 해서는 안된다. 예를 들어, 한 스레드가 네트워크를 통해 전송된 요청의 응답을 기다리고 있는 상황이라면, CPU도 어떠한 작업을 하지 않고 대기 중이 된다. 이때는 다른 스레드를 CPU가 처리할 수 있도록 스위칭을 해야 한다.
2. 멀티 코어 CPU의 활용도를 향상할 수 있다.
하나의 애플리케이션이 최대한 많은 CPU 코어를 사용할 수 있도록 여러 스레드로 작업을 쪼개서 CPU 사용량을 극대화할 수 있다.
3. 높은 응답성을 이용하여 사용자 경험을 향상할 수 있다.
스레드가 요청에 대한 응답을 기다리고 있는 경우, 다른 스레드가 그동안 다른 사용자의 요청을 처리하며 사용자 입장에서 끊김 없이 프로그램을 이용할 수 있다.
멀티 스레드의 문제점
1. 동시성 문제
멀티 스레드를 사용하면 일부 프로그램의 성능을 향상할 수 있다. 하지만, 동일한 프로그맴 내에서 실행되므로 동일한 메모리 공간을 동시에 읽게 된다. 이로 인해서 단일 스레드에서는 발생하지 않는 문제들이 발생할 수 있다.
예를 들어, 스레드가 쓰기 작업을 하는 동안 다른 스레드가 동일한 위치의 메모리 공간을 읽는다면 어떤 값을 읽게 되는 걸까? 만약 동일한 메모리 공간에 동시에 두 개의 스레드가 쓰기 작업을 수행하면 어떤 값이 메모리에 남을까?
이런 혼란을 개발자는 적절하게 막아야 한다. 즉, 공유 자원에 접근하는 방법을 제어하는 것을 알아야 한다. 이에 대해서는 후속 글에서 다룰 것이다.
2. 컨텍스트 스위칭에 대한 오버헤드
CPU가 한 스레드를 실행하다가 다른 스레드로 전환할 때 현재 스레드의 데이터, 프로그램 포인터 등을 저장해야 한다. 이러한 데이터를 컨텍스트라고 하며, 현재 스레드의 컨텍스트는 저장을 하고, 전환할 스레드의 컨텍스트를 불러와야 하는 오버헤드가 존재한다.
자바에서 스레드
자바는 멀티스레드를 지원한다. 과거에는 웹 서버를 CGI(Common Gateway Inteface) 방식으로 개발하여 사용자가 한번 요청할때마다 서버에 프로세스가 하나씩 생겼다. 웹 서비스가 폭발적으로 성장함에 따라, 프로세스를 생성하는 CGI 방식은 서버에 많은 부담이 되었고, 프로세스를 생성하는 비용보다 저렴한 멀티 스레드를 지원하는 자바가 뜨게 된다.
즉, 자바로 웹 서버를 만들면, 사용자 요청마다 스레드를 하나씩 만들게 된다. 같은 자원을 가지고 더 많은 처리를 할 수 있게 된것이다.
스레드 구현하기
자바에서 스레드를 구현하는 방식은 크게 2가지가 있다.
1. Thread 클래스를 상속한다.
public class ThreadTest extends Thread{
@Override
public void run() {
/* 작업 내용 */
}
public static void main(String[] args) {
ThreadTest thread1 = new ThreadTest(); // 스레드 생성
thread1.start(); // 스레드 시작
}
}
2. Runnable 인터페이스를 구현한다.
public class ThreadTest implements Runnable{
@Override
public void run() {
/* 작업 내용 */
}
public static void main(String[] args) {
Thread thread1 = new Thread(new ThreadTest()); // 스레드 생성
thread1.start(); // 스레드 시작
}
}
Thread 클래스를 상속하면 다른 클래스를 상속받지 못하기 때문에 Runnable 인터페이스를 사용하는 것이 좋다. 오버라이딩한 run 메서드에 스레드로 작업할 내용을 적어주면 된다.
Runnable 인터페이스를 생성하는 것은 스레드를 생성하는 것이 아니라 run 메서드를 정의하는 것이기 때문에 따로 스레드 객체를 생성해야 한다.
start()를 호출해야 스레드가 작업을 시작하고, 스레드가 언제 시작할지는 OS 스케줄러가 결정한다. 즉, start()를 호출했다고 즉시 실행되는 것이 아니고, 가장 먼저 start() 메서드를 호출했다고 먼저 실행되는 것은 아니다.
Runnable 인터페이스를 이용한 스레드 테스트
Runnable 인터페이스를 이용해서 간단하게 스레드를 테스트해보겠다.
public class ThreadTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Finish!!!");
}
public static void main(String[] args) {
Thread thread = new Thread(new ThreadTest());
Thread thread1 = new Thread(new ThreadTest());
thread.start();
thread1.start();
}
}
싱글 스레드로 작성이 되었다면 순차적으로 작업이 일어나겠지만, 멀티 스레드를 만들어 작업을 진행하였기 때문에 아래와 같은 결과가 나온다. OS 스케줄러의 상황에 따라 스레드를 실행할 때마다 결과가 다르게 나온다.
[싱글 스레드]
Thread-0 Start!!!
Thread-0 Finish!!!
Thread-1 Start!!!
Thread-1 Finish!!!
[멀티 스레드 - thread 0 먼저 실행됐을 경우]
Thread-0 Start!!!
Thread-1 Start!!!
Thread-0 Finish!!!
Thread-1 Finish!!!
[멀티 스레드 - thread 1 먼저 실행됐을 경우]
Thread-1 Start!!!
Thread-0 Start!!!
Thread-1 Finish!!!
Thread-0 Finish!!!
start()를 실행하는 이유
우리가 오버라이딩을 통해 run() 메서드를 구현하였는데, 정작 실행하는 것은 start() 메서드이다. 그 이유가 뭘까?
start() 메서드를 실행하면 다른 스택에 run() 메서드가 생성되어 호출된다. 그 후, start() 메서드는 종료가 되고 main이 존재하는 스레드와 별개로 run() 메서드가 다른 스레드에서 독립적으로 수행할 수 있게 된다.
만약, start() 메서드를 호출하지 않고 바로 run() 메서드를 호출하면 main이 존재하는 스레드에서 실행하게 되어 싱글 스레드로 돌아가게 된다.
스레드 우선순위
작업의 중요도에 따라 스레드의 우선순위를 다르게 주어 특정 스레드가 더 많은 작업을 처리할 수 있도록 한다. 자바에서는 스레드의 우선순위를 1~10으로 나타내며 10이 가장 우선순위가 높다. 스레드를 만들면 기본으로 우선순위가 5로 설정되고 setPriority()메서드를 통해 우선 순위를 설정할 수 있다. 현재 스레드의 우선순위를 보고 싶으면 getPriority()메서드를 사용하면 된다. 스레드가 시작된 이후에도 우선순위를 변경할 수 있다.
Thread thread = new Thread(new ThreadTest());
thread.start();
System.out.println(thread.getPriority()); // 5
thread.setPriority(10);
System.out.println(thread.getPriority()); // 10
스레드 그룹
여러 스레드를 하나의 그룹으로 묶어서 관리할 수 있다. 모든 스레드는 하나의 스레드 그룹에 포함되어 있어야하며, 만약 생성시 그룹을 지정하지 않았다면 자신을 생성한 스레드(부모 스레드)의 그룹과 우선순위를 상속받는다. getThreadGroup()메서드로 자신이 속한 스레드 그룹을 알 수 있다.
스레드 그룹은 다양한 기능을 지원해준다. 자세한 내용은 여기를 참고하자.
스레드 종류
스레드는 크게 사용자 스레드와 데몬 스레드가 존재한다.
1. 사용자 스레드
main 메서드가 존재하는 스레드를 메인 스레드라고 부르는데, 이 메인 스레드 또한 사용자 스레드이다. 프로그램은 사용자 스레드가 하나도 없을 때 종료된다.
2. 데몬 스레드
사용자 스레드가 하는 일을 보조 하는 역할을 한다. 데몬 스레드가 존재하여도 사용자 스레드가 하나도 없다면 프로그램은 종료된다.
'Computer Science > Operating System' 카테고리의 다른 글
운영체제 - 프로세스 스케줄링 (0) | 2021.09.21 |
---|---|
운영체제 - 병행프로세스, 상호배제 (0) | 2021.09.18 |
운영체제 - 프로세스, 스레드 (2022-04-18 업데이트) (0) | 2021.09.16 |
운영체제 (0) | 2021.09.14 |