Managed Threads에서 Independent Tasks로: Java에서 Go로 Concurrency 재고 (Part 1)

발행: (2025년 12월 28일 오전 08:06 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)

간단하지만 실제적인 문제

우선 매우 흔한 요구사항부터 시작해 보겠습니다: 여러 작업을 동시에 실행하고 모든 작업이 끝날 때까지 기다리는 것. 이 패턴은 어디서든 나타납니다 — 시작 작업을 실행하거나, 여러 API를 호출하거나, 백그라운드 작업을 병렬로 수행할 때 등.

Java에서 이것을 해결하는 방법

When I write this code, I’m very aware that I’m working with threads:

  • 나는 명시적으로 Thread 객체를 생성한다
  • 나는 start() 를 호출하는 것을 기억해야 한다
  • 완료를 기다리기 위해 각 스레드에 join() 해야 한다

나는 그들의 수명 주기를 책임진다. Java에서의 동시성은 작업자를 관리하는 것과 같다. 스레드는 눈에 보이며, 이를 올바르게 다루는 것이 업무의 일부이다.

Java Thread

Go에서 동일한 문제

  • 나는 스레드를 만들지 않는다
  • go 키워드를 사용해 작업을 시작한다
  • 개별 워커를 기다리지 않는다

모든 작업이 끝날 때까지 한 번만 기다린다. Goroutine은 가볍고 일회성처럼 느껴진다. 코드는 무엇이 동시에 실행되는지에 초점을 맞추고, 어떻게 스레드가 관리되는지는 다루지 않는다.

Go routine

동시성 vs 병렬성

Java와 Go 모두에서, 이 예제는 동시성을 나타냅니다 — 독립적으로 진행될 수 있는 작업들입니다. 실제로 병렬로 실행되는지는 CPU 코어 수와 런타임 스케줄러에 따라 달라집니다.

How Concurrency Is Actually Handled in Java vs Go

Java

  • Thread는 JVM과 운영 체제가 관리하는 실제 실행 단위입니다.
  • start()를 호출하면 스레드가 스케줄되어 자체 스택을 가지고 실행됩니다.
  • 메인 스레드는 join()을 호출해 “여기서 멈추고 이 특정 스레드가 끝날 때까지 기다린다.”고 합니다.
  • 시작된 모든 스레드는 명시적으로 join해야 합니다.

Java에서 동시성은 매우 스레드 중심으로 느껴집니다 — 스레드를 만들고, 추적하고, 남아 있는 스레드가 없도록 관리합니다.

Go

  • go 키워드는 goroutine을 시작합니다. 이는 OS 스레드가 아니라 가벼운 실행 단위이며, Go 런타임에 의해 소수의 OS 스레드에 스케줄됩니다.
  • 개별 goroutine을 join하는 대신, Go는 sync.WaitGroup을 사용합니다.
  • wg.Add(n)은 (goroutine을 지정하지 않고) 완료해야 할 작업 단위 수를 선언합니다.
  • 각 goroutine은 작업이 끝나면 wg.Done()을 호출합니다.
  • 메인 함수는 wg.Wait() 한 번만 호출해 모든 작업이 끝날 때까지 블록됩니다.

관심사가 **“어떤 스레드를 기다리고 있나요?”**에서 **“모든 작업이 끝났나요?”**로 이동합니다.

comparison

내가 눈치챈 사고 방식의 전환

이처럼 작은 예시만으로도 차이는 명확합니다. Java와 Go의 가장 큰 차이는 문법이 아니라, 동시성 코드를 작성할 때 집중하는 점입니다.

  • Java: 생각이 스레드에서 시작됩니다 — 스레드가 몇 개인지, 각 스레드가 언제 시작하고 언제 끝나는지.
  • Go: 주의가 작업으로 옮겨갑니다 — 동시에 실행될 수 있는 작업이 무엇인지, 진행 중인 작업이 몇 개인지, 모든 작업이 언제 완료되는지.

이 비교가 나에게 이해시킨 것

이 예제를 진행하면서, 스레드 관리에서 작업 조정으로 전환하는 것이 동시성을 이해하고 사고하기 쉽게 만든다는 것을 깨달았습니다.

이 예제 외에 이것이 중요한 이유

같은 아이디어가 Go에서 반복해서 나타납니다. 동시성은 종종 스레드를 직접 관리하기보다 독립적인 작업을 식별하고 그 완료를 조정하는 것부터 시작합니다. 이 사고방식을 채택하면 동시 Go 코드를 읽고 쓰는 것이 훨씬 원활해집니다.

Part 1의 요점

  • Java는 스레드와 그 수명 주기를 기준으로 생각하게 합니다.
  • Go는 작업과 완료를 기준으로 생각하게 합니다.
  • WaitGroup무엇을 끝내야 하는지에 집중하게 하고, 어떻게 실행되는지는 신경 쓰지 않게 도와줍니다.

In Part 2, 동시 작업이 데이터를 공유해야 할 때 어떤 일이 발생하는지, 그리고 Java와 Go가 그 문제에 어떻게 다르게 접근하는지 살펴보겠습니다.

Back to Blog

관련 글

더 보기 »