Go에서의 Concurrency 파트 1: 기본 및 동기화

발행: (2026년 5월 1일 AM 10:25 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

Concurrency와 Synchronization 기본

먼저 이 두 용어를 이해해봅시다.

Concurrency

정의는 동시에 여러 작업을 관리할 수 있는 능력이다.
각 작업이 반드시 동시에 실행될 필요는 없으며, 작업을 번갈아 수행하는 것만으로도 concurrency라고 할 수 있다.
핵심 특징은 각 작업의 시작 시점이 overlap(겹치는) 것이다.

Parallelism

정의는 하드웨어를 이용해 여러 작업을 동시에 처리할 수 있는 능력이다.

※ 참고: concurrency는 parallelism일 수도, 아닐 수도 있으며, 하드웨어와 매핑되는 방식에 따라 다릅니다.

비록 병렬로 실행되지 않더라도 concurrent하게 실행하면 작업 속도가 빨라질 수 있다. 일반적인 작업은 메모리에서 값을 읽거나 쓰는 등 대기 시간이 필요하기 때문에, 대기 중에 다른 작업을 전환하면 전체 효율이 향상됩니다.

Go에서의 Concurrency

Go는 외부 라이브러리 없이 내장된 concurrency를 제공합니다.

GOMAXPROCS

동시에 Go 코드를 실행할 수 있는 OS 스레드 수를 지정합니다.

runtime.GOMAXPROCS(4)   // 코드에서 설정

또는 환경 변수로 설정합니다.

export GOMAXPROCS=4

Go 1.5부터 GOMAXPROCS의 기본값은 머신의 논리 CPU 수와 동일하므로 별도로 설정할 필요가 없습니다. 값을 너무 높게 설정하면 컨텍스트 스위칭이 증가해 성능이 저하될 수 있습니다.

Goroutine

Go는 goroutine을 사용합니다. 이는 경량 스레드이며(하나의 OS 스레드 안에 수천~수만 개가 존재할 수 있음) Go 런타임 스케줄러가 작업을 스케줄링합니다.

프로그램이 시작될 때 main()에서 Go는 main routine이라 불리는 첫 번째 goroutine을 생성합니다. concurrent하게 실행하려면 함수 호출 앞에 go 키워드를 붙이면 됩니다.

기본 예제

package main

import (
	"fmt"
	"time"
)

func main() {
	go fmt.Println("New Routine")
	fmt.Println("Main Routine")
}

출력은 다음과 같이 보일 것입니다.

Main Routine

main()이 새 goroutine이 실행되기 전에 종료되기 때문입니다.

goroutine을 올바르게 대기하는 방법

time.Sleep 사용은 권장되지 않습니다. 대기 시간이 불확실하기 때문입니다. 대신 sync.WaitGroup과 같은 동기화 메커니즘을 사용하는 것이 적절합니다.

sync 패키지를 이용한 Synchronization

sync.WaitGroup

WaitGroup은 기다려야 할 작업 수를 카운트합니다.

  • Add(n) – 카운트를 증가시킴
  • Done() – 작업이 끝났을 때 카운트를 감소시킴
  • Wait() – 카운트가 0이 될 때까지 대기

예제

package main

import (
	"fmt"
	"sync"
)

func foo(wg *sync.WaitGroup) {
	fmt.Println("New Routine")
	wg.Done() // 작업이 끝났음을 알림
}

func main() {
	var wg sync.WaitGroup
	wg.Add(2)          // 기다릴 작업이 2개
	go foo(&wg)
	go foo(&wg)
	wg.Wait()          // 모든 작업이 끝날 때까지 대기
	fmt.Println("Main Routine")
}

출력

New Routine
New Routine
Main Routine

sync.Once

Once는 지정된 함수가 여러 goroutine에서 호출되더라도 한 번만 실행되도록 보장합니다.

예제

package main

import (
	"fmt"
	"sync"
)

func setup() {
	fmt.Println("Setup")
}

func main() {
	var once sync.Once
	var wg sync.WaitGroup

	for i := 0; i  // (코드 예제가 원본에서 완전하지 않음)
}

※ 참고: Go에서는 중앙 변수를 공유하고 직접 값을 수정하는 것을 피하고, 대신 channel을 사용합니다. 이는 다음 글에서 설명합니다.

0 조회
Back to Blog

관련 글

더 보기 »

Go에서 Concurrency 파트 2: Channel

채널 “메모리를 공유함으로써 소통하지 말고, 소통함으로써 메모리를 공유하라.” Go에서 goroutine은 일반적으로 mutex보다 채널을 통해 데이터를 교환한다.

Go를 제로부터 시작하기: var vs :=

Go에는 변수를 선언하는 두 가지 방법이 있습니다: var와 :=. 이들은 서로 다른 이유로 존재하며 규칙도 다릅니다. 언제 각각을 사용해야 하는지 알면 사소한 실수를 피할 수 있습니다.

개발자로서 당신의 Fear Score는?

두려움은 우리에게 모든 것을 앗아갑니다. 나는 한 번 “You miss 100% of the shots you don’t take”라는 말을 들었고, 그것이 내 경력 전반에 울려 퍼졌습니다. 돌아보면, 나는 man…

TanStack Start와 Bun을 Railway에 배포

문제 Railway의 Nixpacks 자동 빌드가 Bun 프로젝트를 감지하지만, 이 사이트가 필요로 하는 순서를 처리할 수 없습니다: - 빌드 시 prisma generate - Vite + TanStack ...