Managed Threads에서 Independent Tasks로: Java에서 Go로 Concurrency 재고 (Part 1)
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에서의 동시성은 작업자를 관리하는 것과 같다. 스레드는 눈에 보이며, 이를 올바르게 다루는 것이 업무의 일부이다.

Go에서 동일한 문제
- 나는 스레드를 만들지 않는다
go키워드를 사용해 작업을 시작한다- 개별 워커를 기다리지 않는다
모든 작업이 끝날 때까지 한 번만 기다린다. Goroutine은 가볍고 일회성처럼 느껴진다. 코드는 무엇이 동시에 실행되는지에 초점을 맞추고, 어떻게 스레드가 관리되는지는 다루지 않는다.

동시성 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()한 번만 호출해 모든 작업이 끝날 때까지 블록됩니다.
관심사가 **“어떤 스레드를 기다리고 있나요?”**에서 **“모든 작업이 끝났나요?”**로 이동합니다.

내가 눈치챈 사고 방식의 전환
이처럼 작은 예시만으로도 차이는 명확합니다. Java와 Go의 가장 큰 차이는 문법이 아니라, 동시성 코드를 작성할 때 집중하는 점입니다.
- Java: 생각이 스레드에서 시작됩니다 — 스레드가 몇 개인지, 각 스레드가 언제 시작하고 언제 끝나는지.
- Go: 주의가 작업으로 옮겨갑니다 — 동시에 실행될 수 있는 작업이 무엇인지, 진행 중인 작업이 몇 개인지, 모든 작업이 언제 완료되는지.
이 비교가 나에게 이해시킨 것
이 예제를 진행하면서, 스레드 관리에서 작업 조정으로 전환하는 것이 동시성을 이해하고 사고하기 쉽게 만든다는 것을 깨달았습니다.
이 예제 외에 이것이 중요한 이유
같은 아이디어가 Go에서 반복해서 나타납니다. 동시성은 종종 스레드를 직접 관리하기보다 독립적인 작업을 식별하고 그 완료를 조정하는 것부터 시작합니다. 이 사고방식을 채택하면 동시 Go 코드를 읽고 쓰는 것이 훨씬 원활해집니다.
Part 1의 요점
- Java는 스레드와 그 수명 주기를 기준으로 생각하게 합니다.
- Go는 작업과 완료를 기준으로 생각하게 합니다.
WaitGroup은 무엇을 끝내야 하는지에 집중하게 하고, 어떻게 실행되는지는 신경 쓰지 않게 도와줍니다.
In Part 2, 동시 작업이 데이터를 공유해야 할 때 어떤 일이 발생하는지, 그리고 Java와 Go가 그 문제에 어떻게 다르게 접근하는지 살펴보겠습니다.