Etc.

[CS] 스레드(Thread) 개념 정리

haehyun 2022. 1. 17. 22:55
728x90

프로그램, 프로세스, 스레드

프로그램이 실행되면 프로세스가 되고, 프로세스에서 여러 개의 스레드를 생성해 작업을 수행한다.

 

프로그램(Program)

  • 프로그래밍 결과물로써 특정 기능을 하는 실행파일(.exe)

파일탐색기 프로그램 Notion.exe

 

프로세스(Process)

  • 실행중인 프로그램 인스턴스
  • 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.
  • 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 스레드로 구성되어 있다.
  • 하나의 프로그램에서 여러 개의 프로세스가 생성될 수 있다.
  • [Alt + Shift + Esc] 단축키로 [작업관리자] 창에서 프로세스를 확인할 수 있다.
  • 운영체제는 실행중인 프로세스들을 PID(Process ID)로 구분한다.

[작업관리자] > [프로세스]
하나의 프로그램에서 2개의 프로세스가 생성됨

 

스레드(Thread)

  • 프로세스의 자원을 이용해서 실제로 작업을 수행한다.
  • 모든 프로세스에는 최소 하나의 스레드가 존재한다. (=메인 스레드)
  • 멀티스레드 프로세스(Multi-threaded process) : 둘 이상의 스레드를 가진 프로세스.
  • 각 스레드는 작업을 수행하는데 개별적인 메모리 공간(호출스택)을 필요로 한다.
  • 이미 실행중인 스레드에서 새로운 스레드를 만들 수 있다.
  • 하나의 스레드가 가질 수 있는 스레드의 개수는 제한이 없으나, 프로세스의 메모리 한계에 따라 생성할 수 있는 스레드 개수가 결정된다.

 

멀티태스킹(Multi-tasking)

  • 여러 개의 프로세스가 동시에 실행되는 것.
  • CPU의 코어(core)가 한 번에 단 하나의 작업만 할 수 있기 때문에 실제로는 운영체제의 스케줄링에 따라 빠르게 번갈아가며 실행되어 사용자에게는 동시에 실행되는 것처럼 보이게 한다.
  • 다수의 프로세스가 CPU와 같은 공용 자원을 나누어 사용한다.

 

멀티스레딩(Multi-threading)

  • 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것.
  • 여러 스레드가 같은 프로세스 내에서 자원을 공유한다. (자원을 번갈아가며 사용)
  • 멀티태스킹과 같은 이유로 실제로 동시에 처리되는 작업의 개수는 CPU 코어의 개수와 같다. (1코어=1작업)
    => 프로세스의 성능이 스레드 개수에 비례하지 않는 이유.
  • 멀티스레딩 장점
    • CPU의 사용률 향상
    • 자원을 효율적으로 사용
    • 사용자에 대한 응답성 향상
    • 작업의 분리로 코드 간결화
  • 멀티스레딩 단점
    • 동기화, 교착상태 문제 발생 가능성有
  • 하나의 프로그램에서 여러 작업을 동시에 발생/수행할 때 멀티스레딩이 필요하다. 메신저로 채팅을 하는 동시에 파일을 다운로드 받거나 음성전화를 할 수 있는 이유는 해당 프로그램이 멀티스레딩으로 작성되어 있기 때문이다. 싱글스레드의 경우 파일을 다운받기 시작하면 프로그램이 해당 작업에만 매진하여 다운로드 작업이 끝날 때까지 채팅 등 다른 작업을 할 수 없다.
  • 여러 사용자에게 서비스를 해주는 서버 프로그램의 경우 멀티스레딩 필수. 하나의 서버 프로세스가 여러 개의 스레드를 생성해 1사용자=1스레드로 요청이 처리되도록 해야 한다. 그렇지 않으면 1사용자=1프로세스로 매 요청마다 새로 프로세스를 생성해서 서비스를 제공해야 할텐데, 프로세스를 생성하는 건 스레드를 생성하는 것보다 더 많은 자원, 시간을 소모하기 때문에 부담이 크다. (스레드 = 경량 프로세스)

 


Java에서 스레드 구현과 실행

스레드 구현 방법

Thread 클래스를 상속(extends)받거나, Runnable 인터페이스를 구현(implement)해서 run 메서드를 스레드를 통해 작업하고자 하는 내용으로 오버라이딩한다.

A. Thread 클래스를 상속받는 방법

class MyThread extends Thread {
    @Override
    public void run() { /* 구현 */ }
}

 

B. Runnable 인터페이스를 구현하는 방법

class MyThread implements Runnable {
    @Override
    public void run() { /* 구현 */  }
}

 

스레드 구현 후 생성

A. Thread 클래스를 상속받은 MyThread1 클래스의 인스턴스를 생성

MyThread t1 = new MyThread1();

 

B. Runnable 인터페이스를 구현하는 MyThread2 클래스의 인스턴스를 생성해서 Thread 생성자의 매개변수로 넘김

Runnable r = new MyThread2();
Thread t2 = new Thread(r);
// Thread t2 = new Thread(new MyThread2())

 

public class ThreadEx1 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1(); // A. Thread 상속 클래스 인스턴스

        Runnable r = new MyThread2();   // B-1. Runnable 구현 클래스 인스턴스
        Thread t2 = new Thread(r);      // B-2. Thread(Runnable target) 생성자

        t1.start();
        t2.start();
    }
}

// A. Thread 클래스를 상속받는 방법
class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName());  // 부모 클래스의 getName() 메서드 호출
        }
    }
}

// B. Runnable 인터페이스를 구현하는 방법
class MyThread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName());   // Thread 클래스의 static 메서드 호출
        }
    }
}
// 실행결과
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1

 

  • String getName() : 스레드의 이름을 반환한다.
  • static Thread currentThread() : 현재 실행중인 스레드의 참조를 반환한다.
  • void start() : 스레드를 실행한다. (=실행대기 상태에 들어가 자신의 차례가 되면 실행된다.)
  • Thread를 상속받는 방법을 사용한 MyThread1클래스는 부모클래스인 Thread 클래스로 부터 물려받은 getName() 메서드를 바로 사용 가능하나, Runnable 인터페이스를 구현하는 방법을 사용한 MyThread2클래스는 Thread 클래스를 상속받지 않아 getName() 메서드가 없기에 Thread 클래스의 static 메서드 currentThread()를 호출해 Thread 인스턴스를 반환받아 사용한다.
  • 스레드 실행순서는 OS의 스케쥴러가 작성한 스케쥴에 의해 결정된다.
  • 한 번 실행이 종료된 스레드는 다시 실행할 수 없다. 하나의 스레드에 대해 start() 호출은 한 번만 가능. (하나의 스레드에서 start()를 두 번 호출하면 IllegalThreadStateException 예외 발생)

 

생성한 스레드에 이름을 지정할 수 있다. 별도로 이름을 지정하지 않을 시 기본적으로 "Thread-{번호}" 형식으로 지정됨.

public class ThreadEx1 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("ExtendsThread");			// setName(String name)

        Runnable r = new MyThread2();
        Thread t2 = new Thread(r, "ImplementThread");	// Thread(Runnable r, String name)

        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread { /* */ }
class MyThread2 implements Runnable { /* */ }
// 실행결과
ExtendsThread
ExtendsThread
ExtendsThread
ExtendsThread
ExtendsThread
ImplementThread
ImplementThread
ImplementThread
ImplementThread
ImplementThread

 

728x90