싱글 스레드와 멀티 스레드
Last updated
Last updated
먼저, 싱글 스레드 애플리케이션은 말 그대로 하나의 스레드만을 사용해서 작업을 처리하는 프로그램입니다. 즉, 한 번에 하나의 작업만을 수행할 수 있습니다. 이 방식은 구조가 단순하고 이해하기 쉬운 장점이 있지만, 작업이 많아지거나 시간이 오래 걸리는 경우에는 효율이 떨어질 수 있습니다.
반면에 멀티 스레드 애플리케이션은 여러 개의 스레드를 동시에 사용해서 작업을 나누어 처리하는 프로그램입니다. 즉, 파일을 읽는 동안 다른 스레드가 데이터를 처리하거나, 동시에 여러 사용자의 요청을 다룰 수 있습니다. 이 방식은 CPU와 자원을 더 효율적으로 활용할 수 있다는 장점이 있지만, 스레드 간의 동기화나 충돌을 관리해야 하기 때문에 코드가 좀 더 복잡해질 수 있다는 단점이 있습니다.
예를 들어, Redis나 NestJS 같은 경우는 싱글 스레드 방식으로 구현되어 있어서 단순하고 빠른 작업 처리에 강점을 가지고 있습니다. 반면에 Spring 같은 프레임워크는 멀티 스레드 방식으로 설계되어 있어서 복잡한 요청이나 다중 사용자 환경에서 더 유연하게 동작할 수 있습니다
멀티 스레드 애플리케이션에서는 여러 스레드가 동시에 실행되면서 동일한 데이터나 자원에 접근할 수 있습니다. 이는 멀티 스레드의 장점이기도 하지만, 동시에 문제가 발생할 수 있는 지점이기도 합니다. 특히 스레드들이 공유 자원을 동시에 접근하려 할 때 대표적으로 발생하는 문제는 레이스 컨디션, 영어로는 ‘race condition’이라고 합니다.
레이스 컨디션은 여러 스레드가 동일한 데이터를 수정하려고 할 때, 어떤 스레드가 먼저 실행되느냐에 따라 결과가 달라지는 상황을 의미합니다. 예를 들어, 숫자 하나를 1씩 증가시키는 작업을 생각해 보겠습니다. 초기 값이 0이고, 두 스레드가 동시에 이 값을 1씩 증가시키려 한다고 가정하겠습니다. 첫 번째 스레드가 0을 확인하고 “1로 변경해야겠다”고 계산을 시작하는 순간, 두 번째 스레드도 0을 보고 “1로 변경해야겠다”며 값을 덮어쓸 수 있습니다. 그러면 결과는 2가 되어야 하지만, 타이밍이 엉키면서 1로 끝날 수도 있습니다. 이것이 바로 레이스 컨디션입니다.
이런 문제를 해결하려면 스레드 간 실행 순서를 제어해야 합니다. 이를 위해 몇 가지 대표적인 방법을 사용합니다. 먼저, ‘synchronized’ 키워드를 활용하는 방식이 있습니다. 이 키워드를 사용하면 특정 코드 블록에 한 번에 한 스레드만 접근할 수 있도록 제한합니다. 예를 들어, 숫자를 변경하는 코드를 synchronized로 감싸면 첫 번째 스레드가 작업을 완료할 때까지 두 번째 스레드가 대기하게 됩니다.
또 다른 방법으로는 ‘Lock’을 사용하는 방식이 있습니다. 자바의 Lock 인터페이스를 활용하면 락을 걸고 해제하면서 보다 세밀하게 스레드를 제어할 수 있습니다. synchronized보다 유연성이 높아 복잡한 상황에서 유용합니다. 예를 들어, 데이터를 수정하기 전에 락을 걸고 작업이 끝나면 락을 해제하는 식으로 진행합니다.
마지막으로, 동기화 없이도 안전하게 처리할 수 있는 ‘AtomicInteger’ 같은 클래스를 활용하는 방법이 있습니다. 이는 원자적 연산을 제공하여 스레드 간 충돌 없이 값을 안전하게 변경할 수 있도록 도와줍니다. 코드가 간단하면서도 효율적이라는 장점이 있습니다.