-
Notifications
You must be signed in to change notification settings - Fork 0
쓰레드 (Thread)
-
cf) 프로세스(process) : 실행중인 프로그램
- 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것
- 프로그램에 사용되는 데이터와 메모리 등의 자원, 쓰레드로 구성
-
쓰레드 (thread): 프로세스 내에서 실제로 작업을 수행하는 주체
- 프로세스에는 하나 이상의 쓰레드가 존재하여 작업을 수행함.
- 이때, 두 개 이상의 쓰레드를 가지는 프로세스를 멀티쓰레드 프로세스(multi-threaded process)라고 함.
-
쓰레드는 user thread, kernel thread 두 종류가 있음.
- 커널 영역의 상위에서 지원되며, 일반적으로 사용자 레벨의
라이브러리를 통해 구현되며, 라이브러리는 스레드 생성 및 스케줄링 관련 관리 기능을 제공함. - 동일한 메모리 영역에서 쓰레드가 생성 및 관리되므로 속도가 빠름. (스레드가 생성된 프로세스에 의해 제어됨)
- 하나의 스레드가 block되면 다른 모든 스레드도 block되게 된다.
- 커널에서는 사용자 스레드의 인식하지 못하기 때문에 해당 프로세스를 block시키기 때문
- 프로세스 1개에 커널 스레드 1개가 할당되어, 같은 프로세스 내의 스레드 n개 가 1개의 커널 스레드를 공유함.
- 운영체제가 지원하는 스레드 기능으로 구현되며, 커널이 스레드의 생성 및 스케줄링 등을 관리
- 따라서 스레드가 중단되더라도, 커널은 프로세스 내의 다른 스레드를 중단시키지 않고 계속 실행시켜 준다.
- 멀티 프로세싱 환경에서 커널은 여러 개의 스레드를 각각 다른 CPU에 할당할 수 있다.
- 하지만 사용자 스레드에 비해 생성 및 관리가 느리다는 단점이 있음
-
Runnable Interface를 구현하는 방법
-
보통 Runnable Interface를 구현하는 방법을 더 많이 씀
(Thread class를 상속받으면 다른 클래스를 상속할 수 없으므로)
-
Runnable은 아래와 같이 정의되어 있다.
package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }
⇒ Runnable을 구현한 클래스에서 run()을 메소드를 구현한 뒤
Runnable을 구현한 클래스의 인스턴스를 Thread의 생성자로 넘겨
Thread 객체를 생성한 뒤 Thread.start()로 실행할 수 있다.
public class Test implements Runnable {
public void run() {
// Do some task
}
}
public static void main(String[] args {
Thread thread = new Thread(new Test());
thread.start(); // Test.run() 실행
}-
Thread class를 상속받는 방법
-
Thread class는 Runnable Interface를 implement한 클래스
-
Thread는 아래와 같이 정의되어 있다.
package java.lang; public class Thread implements Runnable { ... /* What will be run. */ private Runnable target; ... public void run() { if (target != null) { target.run(); } } ...
⇒ Thread를 상속한 클래스에서 run() 메소드를 구현한 뒤 Thread.start()를 통해 구현한 run()을 실행시킬 수 있다.
-
Java 프로그램 실행시, JVM 프로세스가 동작하고, JVM 프로세스 내에서 GC 등 여러 개의 Thread가 실행됨.
- 프로세스 생성에는 많은 시간, 자원이 필요하기 때문에 여러 개의 프로세스가 아닌, 스레드를 사용
-
스레드가 실행되기 위해서는 CPU 스케줄러가 해당 스레드를 CPU에 스케줄링해야 하는데,
커널에서는 사용자 스레드의 존재를 알지 못하기 때문에, CPU 스케줄링의 대상이 되지 못한다.
따라서 사용자 스레드는 커널 스레드에 매핑되어 실행된다.
-
사용자 스레드와 커널 스레드를 매핑하는 방법에는 아래 3가지가 있다.
- 여러 개의 유저 스레드를 하나의 커널 스레드로 맵핑하는 방식
- 여러 스레드 중 하나가 block되면 전체 프로세스가 block됨
- JVM이 OS 스레드를 사용하지 않고 유저 스레드만을 이용 (java 초기 버전의 스레드 모델)
- 동기화 및 자원공유가 용이 → 실행시간 단축
- 이 경우 멀티 코어 환경에서도 한 번에 하나의 user thread만이 처리될 수 있으므로, 멀티 코어의 장점을 얻을 수 없다는 단점이 있음. (병렬처리 불가)
- 현재는 거의 사용되지 않고 있음
- 유저 스레드 하나를 하나의 커널 스레드에 맵핑하는 방식
- 따라서 하나의 유저 스레드가 block되더라도 다른 스레드가 실행될 수 있어 병렬 실행이 용이하다.
- 각 스레드를 다른 코어에서 동시에 실행시키는 등 멀티코어 환경을 활용할 수 있음!
- 하지만 이 경우 사용자 스레드를 생성할 때마다 커널 스레드를 만들어야 하는데, 커널 스레드 수가 너무 늘어나면 시스템 성능에 부담을 줄 수 있다는 문제가 있음. (스레드 개수를 무한정 늘릴 수는 없다!)
-
여러 개의 유저 스레드를 여러 개의 커널 스레드로 맵핑하는 방식
-
JVM이 OS의 도움을 받아 스레드를 관리 (JDK 1.3 이후 자바의 스레드 모델)
-
many-to-many 방식과 one-to-one 방식의 문제점을 해결하기 위해 등장-!
- many-to-many에서 하나의 스레드가 중단되면 다른 모든 스레드도 중단되는 문제와 one-to-one에서 스레드 개수 제한 문제를 해결!
-
멀티코어 시스템을 활용할 수 있음
-
스레드 동기화 및 자원공유가 복잡 → 실행시간 증가
-
하지만 many-to-many 방식은 나머지 두 모델에 비해 실제로 구현하기가 가장 어렵다.
또한 대부분의 시스템에서 코어수가 늘어나면서 커널 스레드 개수를 제한할 필요성이 줄었다
⇒ 현재 대부분의 시스템에서 one-to-one 모델이 사용되고 있다.