[Java] 쓰레드 (Thread) 1
시스템프로그래밍(시스템 자원을 쓰는것) / 운영체제(Operating System)에서 자세하게 다루는 부분이다. 어떻게 활용하는지에 초점을 맞춰 공부해보자.
프로세스 : 프로그램의 단위
하나의 프로그램이 실행될 때 하나의 프로세스에 할당돼 실행된다. PID(Process ID) 인터넷 익스플로어, 크롬.. 등등 여러 가지 프로그램들이 하나의 프로세스로써 돌아간다.
아니 그러면 한 번에 하나의 프로그램만 쓸 수 있나?? 라고 생각할 수 있다. 과거 DOS라는 운영체제는 멀티태스킹을 지원하지 않아 실제로 한 번에 두 가지 이상의 프로그램을 사용할 수 없었다. 하지만, 운영체제가 Windows 로 넘어가면서 멀티태스킹을 지원해 여러 가지 프로그램들을 한 번에 띄울 수 있게 됐다.
쓰레드는 프로세스를 가볍게 생각한 것, 한 프로그램에서 각각의 기능, 일 하나하나라고 생각하자. 일의 단위라고 생각하면 되겠다.
크롬같은 웹 브라우저를 활용해 파일을 다운받으면서 웹서핑을 하는 경우를 생각해보자. 다운로드를 담당하는 쓰레드가 있고, 웹서핑을 담당하는 쓰레드가 있고 멀티쓰레딩을 통해 두 가지 동작을 한 번에 처리할 수 있다.
우리가 지금까지 이클립스를 통해서 코드를 짜고... 실행시키고... 했던 자바 프로그램들은 싱글쓰레드 안에서 돌아갔다.
즉, 한 번에 최대 하나의 기능만 수행할 수 있는 프로그램을 작성해왔다.
하지만, 덧셈을 하면서 뺄셈을 하는 프로그램을 작성할 수 없을까? 기존의 프로그램처럼 덧셈을 하고 뺄셈 연산을 하는 식으로 하나의 쓰레드로 두 가지 연산을 수행하는 프로그램 말고, 두 가지 작업을 동시에 실행하게 하는 프로그램을 만들 수는 없을까?
이 때 멀티쓰레딩이 사용될 수 있다. 쓰레드1 에는 덧셈을, 쓰레드2에는 뺼셈을 담당하게 하면 위의 과정을 한 번에 처리할 수 있다.
프로세스와 쓰레드에 대해 좀 더 자세히 알아보자.
프로세스는 무겁다 라고 표현하면 좋을 것 같다. 프로그램을 짜면서 생성했던 스택, 힙 공간... 등등은 프로세스를 하나 새로 만들면 완전히 독립되어 새롭게 생성된다.
하나의 프로그램에서 여러 개의 쓰레드를 돌리면 각각의 쓰레드는 다른 일을 하지만, 하나의 프로세스 안에서 동작하기 때문에 쓰레드끼리는 공간을 공유한다. 어떤 함수가 실행될 때 각각의 쓰레드는 스택 공간을 각자 가지고 있을 수 있지만, 전역변수(지역변수와 다르다. 클래스 안에 정의됐지만 메서드의 바깥에 정의된 변수)는 공유한다. (전역변수는 프로세스마다 구분된다.)
특징을 정리해 보면
1. 하나의 쓰레드를 만드는 것이 하나의 프로세스를 만드는 것 보다 cost소비가 적으니까 쓰레드를 사용한다.
2. 같은 프로세스 내의 쓰레드들은 서로 자원을 공유한다. (전역변수)
3. 한 프로세서는 최대 하나의 쓰레드를 동시에 수행할 수 있다.
3번을 자세히 보면, 요즘은 듀얼코어 쿼드코어 등등 여러 가지 동작을 한 번에 수행할 수 있는 코어들이 등장했지만, 싱글코어에서는 CPU 하나당 최대 하나의 쓰레드를 동시에 처리할 수 있었다.
아니 그럼 싱글코어에서는 어차피 쓰레드는 최대 하나밖에 못쓰는데 멀티쓰레드를 쓸 필요가 없지 않나 ???
싱글코어에 쓰레드1과 쓰레드2가 있고, 쓰레드1이 쉬고 있는 경우를 생각해 보자. 쓰레드1이 쉬고있지만 멀티쓰레딩을 통해 쓰레드2는 일할 수 있으니 멀티쓰레드를 사용하는 의의가 있다. (한 프로세서에서 일할 수 있는 쓰레드는 최대 하나지만, 쓰레드는 여러 개 만들 수 있다. 프로세서와 프로세스는 다르다.)
듀얼코어는 동시에 두 개의 쓰레드를 실행시킬 수 있고, 쿼드코어는 동시에 네 개의 쓰레드를 실행시킬 수 있다. 즉, 멀티쓰레드를 동시에 돌아가게 하려면 코어가 많아야 한다.
멀티프로세스와 멀티쓰레드에 대해 알아보자.
하나의 일 당 쓰레드 대신 프로세스를 배치해서 수행할 수 있긴 하다. (쓰레드를 쓰는게 효율적이지만..)
이렇게 처리하는걸 멀티 프로세스라고 한다.
프로세스끼리는 거의 독립돼있기때문에 프로세스1에 발생한 문제가 프로세스2로 번질 위험은 적다.
멀티쓰레드는 멀티프로세스와 비교해서 생각하면 이해가 편할 것 같다. 멀티쓰레드에서 디버깅이 까다롭다고 하는데, 여러 가지 쓰레드 중 어떤 쓰레드를 일하게 하고 어떤 쓰레드를 쉬게 할지 스케쥴링하는게 까다롭기 때문이다.
쓰레드1 이 전역변수를 업데이트하면 다른 쓰레드들도 그 전역변수를 사용하기 때문에 이 방식으로 쉽게 쓰레드들간에 데이터를 주고받을 수 있다.(다른 방법도 있음) 하지만, 멀티프로세서에서 프로세스끼리 데이터를 주고받으려면 따로 통신 프로토콜을 사용해야 한다. IPC 등등.. 이런 과정은 사용하기 까다롭고 이는 cost의 증가로 이어진다.
더 자세한 내용은 심화 과정에서 배우기로 하고, 일단은 아~ 쓰레드는 일의 단위고 여러 개의 일을 만들어서 한 쓰레드가 놀때 다른 쓰레드가 실행할 수 있도록 해 봐야지~ 정도의 인식만 가지고 가자.
자바에서 쓰레드를 사용해보자. 두 가지 방법으로 사용할 수 있다.
1. Thread 클래스를 상속받아 처리할 수 있다.
class thread1 extends Thread {
public void run() {
// 작업내용 작성
}
}
쓰레드가 가지는 run이라는 메서드가 있다. 이 쓰레드가 시작할 때 할 작업을 run에 기록한다.
위처럼 쓰레드가 속한 클래스를 생성하고, main메서드에서 객체를 만들어주자.
public static void main(String[] args) {
thread1 t1 = new thread1();
t1.start();
}
}
start 메서드를 통해 run함수에 있는 내용을 수행하게 하자.
2. Runnable 인터페이스를 구현한다.
class thread2 implements Runnable{
public void run() {
// 작업내용 작성
}
}
이렇게 만들게 되면 쓰레드를 만들 때 인자(매개변수)로 Runnable Object를 받아야 한다.
public static void main(String[] args) {
Runnable r = new thread2();
Thread t2 = new Thread(r);
}
}
위처럼 Runnable 인터페이스를 구현한 thread2를 이용해 Runnable 타입의 참조변수 r을 만들고 매개변수로 집어넣어줬다.
자바는 다중상속이 불가능한 점을 생각해 둘 중 더 괜찮은 방법을 사용하자.
public interface Runnable{
public abstract void run();
}
Runnable 인터페이스의 구성이다. 추상메서드 하나만 포함하고 있다.
즉, 함수형 인터페이스로 볼 수 있다. 그러므로 위의 코드에서 람다식을 활용할 수 있다.
start() : 쓰레드를 시작함
run() : 쓰레드를 시작했을 때 무슨 동작을 할 지 알려준다.
쓰레드를 run 메서드를 통해 실행시키면, 멀티쓰레드가 아니라 현재 쓰레드(main 메서드 내에서의 쓰레드)에서 run()메서드의 내용이 실행되게 된다. 멀티쓰레드를 활용하려면 start()를 이용하자.
위의 내용을 try-catch 문을 통해 확인할 수 있다.
싱글쓰레드와 멀티쓰레드의 차이를 살펴보자.
멀티쓰레드를 사용할 때 따로 스케줄링은 안해줄 시 OS가 알아서 스케줄링 해 준다.
실행 결과는 위의 표를 통해 알 수 있고,
하나의 프로세서에서는 최대 하나의 쓰레드를 사용할 수 있고, 쓰레드끼리 스위칭을 할 때 시간을 많이 소모한다.
즉, A에서B로 바뀌는 그 순간에 시간소모가 크다. (첫 번째 경우는 하나의 쓰레드로 해결하기에 스위칭이 없다.)
그러므로 위의 상황에서는 첫번째 경우의 속도가 더 빠르다고 할 수 있다.
아니 멀티쓰레드 활용하면 더 느린데 왜 쓰지??
하나의 프로세서는 최대 하나의 쓰레드를 사용할 수 있지만, 코어가 늘어남에 따라서 여러 가지 쓰레드를 한 번에 사용할 수 있게 된다.
멀티코어일 경우에는 A를 실행하면서 B를 실행할 수 있다.
쓰레드1은 코어1에, 쓰레드2는 코어2에 이렇게 따로 지정하지 않아도 OS가 쓰레드를 코어에 분배해준다.
OS가 스케줄링도 하고 코어 분배도 하는걸 봤을 때, 멀티쓰레드를 활용했을 때는 실행 결과가 일관적이지 않다는 걸 예측할 수 있다. OS / CPU 상태 등등.. 여러 가지 변수가 있기 때문에 항상 같은 결과가 나오지는 않는다.
자세한 스케줄링은 차차 알아가기로 하고, 배분이 어떻게 진행되는지에 집중해서 공부하자.
싱글코어에서 멀티쓰레드를 활용하는 예시를 살펴보자.
쓰레드 1이 쉴 때 그냥 대기하는 코드와 쓰레드 1이 쉴 때 쓰레드 2가 일할 수 있도록 작성한 코드이다.
위의 경우는 멀티쓰레드를 활용하는게 유리하다.
만약 코드를 짤 때 쓰레드1이 쓰레드2에 영향을 줄 수 있게 작성한다면, 동기화 문제가 발생할 수 있다.
쓰레드의 우선순위를 지정해 줄 수도 있다.
어떤 쓰레드를 우선 실행할 지. 어떤 쓰레드에 CPU 자원을 더 많이 할당할 지를 결정한다.
기본값은 5이고, 1~10 사이의 값으로 우선순위를 지정해줄 수 있다.
현재 우선순위는 getPriority() 메서드로 읽어올 수 있다.
'Programming Language > Java' 카테고리의 다른 글
[Java] 입출력 (I/O) 1 (0) | 2021.11.21 |
---|---|
[Java] 쓰레드 (Thread) 2 (0) | 2021.11.12 |
[Java] 래퍼 클래스 (Wrapper) (0) | 2021.10.29 |
[Java] 예외처리 (0) | 2021.10.29 |
[Java] 객체지향 요약 (0) | 2021.10.29 |
댓글
이 글 공유하기
다른 글
-
[Java] 입출력 (I/O) 1
[Java] 입출력 (I/O) 1
2021.11.21 -
[Java] 쓰레드 (Thread) 2
[Java] 쓰레드 (Thread) 2
2021.11.12 -
[Java] 래퍼 클래스 (Wrapper)
[Java] 래퍼 클래스 (Wrapper)
2021.10.29 -
[Java] 예외처리
[Java] 예외처리
2021.10.29