[Java] Thread (3) - 싱글쓰레드와 멀티쓰레드
이번 글에서는 실제 코드를 통해 싱글쓰레드와 멀티쓰레드에 대해 알아보도록 하겠습니다.
두 개의 작업이 있습니다.
해당 작업을 하나의 쓰레드로 처리하는 경우와 두 개의 쓰레드로 처리하는 경우를 가정하도록 하겠습니다.
하나의 쓰레드로 두 개의 작업을 처리하는 경우에는 한 작업을 모두 마친 후에 다른 작업을 시작하지만,
두 개의 쓰레드로 작업하는 경우에는 짧은 시간동안 2개의 쓰레드가 번갈아가며 작업을 수행하기에, 동시에 두 작업이 처리되는 것과 같이 느끼게 됩니다.
싱글 코어에서 하나의 쓰레드로 두 개의 작업을 실행한 시간과 두개의 쓰레드로 두개의 작업을 수행한 시간을 비교해 보면 거의 동일하지만, 미약하게나마 두 개의 쓰레드로 작업한 시간이 하나의 쓰레드로 작업한 시간보다 더 걸립니다.
이유는 쓰레드간의 Context Switching 때문입니다.
Context Switching이 일어나는 경우, 현재 진행중인 작업의 상태등과 같은 정보를 저장하고 읽어오는데 시간이 소요됩니다.
싱글 코어에서는 단순히 CPU만을 사용하는 계산이라면 멀티쓰레드보다 싱글쓰레드로 작업하는 것이 효율적입니다.
실제 코드를 통해 비교해 보도록 하겠습니다.
싱글 쓰레드 예시
public class Example {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 3000; i++) {
System.out.printf("%s", new String("-"));
}
System.out.println();
System.out.println("소요시간(1) : [" + (System.currentTimeMillis()- start) + "] ms ");
for (int i = 0; i < 3000; i++) {
System.out.printf("%s", new String("|"));
}
System.out.println();
System.out.println("소요시간(2) : [" + (System.currentTimeMillis()- start) + "] ms ");
}
}
위 코드의 결과는 다음과 같습니다.
멀티 쓰레드 예시
public class Example {
static long start;
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3000; i++) {
System.out.printf("%s", new String("-"));
}
System.out.println();
System.out.println("소요시간(1) : [" + (System.currentTimeMillis()- start) + "] ms ");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
start = System.currentTimeMillis();
thread.start();
for (int i = 0; i < 3000; i++) {
System.out.printf("%s", new String("|"));
}
System.out.println();
System.out.println("소요시간(2) : [" + (System.currentTimeMillis()- start) + "] ms ");
}
}
결과는 다음과 같습니다.
싱글 쓰레드 예제와는 달리 아주 짧은 시간동안 번갈아가면서 실행되었으며, 거의 동시에 작업이 완료되었습니다.
왜 두 개의 쓰레드가 더 느릴까?
두 개의 쓰레드로 작업하는것이 느린 것에는 두가지 이유가 있습니다.
하나는 두 쓰레드가 번갈아가면서 작업을 처리하기에 Context Switching이 발생하기 때문입니다.
다른 하나는 한 쓰레드가 화면(console)에 출력하는 동안 다른 쓰레드는 출력이 끝나기를 기다려야 하는데, 이때 발생하는 대기시간 때문입니다.
즉 공통된 화면(console)이라는 자원에 대한 Race Condition이 발생하기 때문입니다.
Race Condition?
Race Condition이란 두 개 이상의 cocurrent한 프로세스(혹은 스레드)들이 하나의 자원(리소스)에 접근하기 위해 경쟁하는 상태를 말합니다.
참고로 여러 쓰레드가 여러 작업을 동시에 진행하는 것을 병행(동시)(concurrent)이라 하고,
하나의 작업을 여러 쓰레드가 나눠서 처리하는 것을 병렬(parallel)이라 합니다.
그러나 두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우에는 싱글쓰레드보다 멀티쓰레드 프로세스가 더 효율적입니다.
예시를 들어보겠습니다.
사용자로부터 입력을 받아들여 이를 출력하는 작업과, 화면에 1부터 10까지 1초 간격으로 출력하는 작업이 있다고 하겠습니다.
이를 하나의 쓰레드로만 작성하면 다음과 같습니다.
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s", new String("|"));
}
}
해당 코드는 만약 사용자가 입력을 10초후에 진행한다면 대략 20초의 시간이 걸릴 것입니다.
그러나 이를 두개의 스레드를 통해 진행하도록 바꿔보겠습니다.
import java.util.Scanner;
public class Example {
static class MyRunnable implements Runnable {
@Override
public void run() {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s", new String("|"));
}
}
}
이렇게 작성한다면 사용자가 입력을 10초뒤에 진행한다고 하면 총 10초정도의 시간동안 두개의 작업을 끝마칠 수 있습니다.