Skip to content

필사 모드: Go 프로그래밍 완전 가이드 — 고루틴, 채널, 인터페이스, 실전 패턴

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

1. 왜 Go인가

Go(Golang)는 2009년 Google에서 만든 언어입니다. 설계 철학은 **단순함, 빠른 컴파일, 강력한 동시성**입니다.

Go가 선택받는 이유

- **Docker, Kubernetes, Terraform, Prometheus** 등 클라우드 인프라의 핵심 도구가 모두 Go로 작성되었습니다

- 컴파일 속도가 매우 빠릅니다. 수십만 줄 프로젝트도 몇 초 안에 빌드됩니다

- 정적 바이너리를 생성하므로 배포가 단순합니다. 별도 런타임이 필요 없습니다

- 고루틴을 통해 수십만 개의 동시 작업을 효율적으로 처리합니다

- 가비지 컬렉션이 있지만, 지연 시간이 매우 짧습니다

Go vs 다른 언어

| 항목 | Go | Python | Java | Rust |

|------|-----|--------|------|------|

| 컴파일 속도 | 매우 빠름 | 인터프리터 | 느림 | 느림 |

| 실행 속도 | 빠름 | 느림 | 빠름 | 매우 빠름 |

| 동시성 | 고루틴 | asyncio | 스레드 | async/tokio |

| 학습 곡선 | 낮음 | 매우 낮음 | 높음 | 매우 높음 |

| 메모리 관리 | GC | GC | GC | 소유권 |

Go는 **생산성과 성능의 최적 균형점**에 위치합니다. Rust만큼 빠르진 않지만, 팀 전체가 빠르게 배우고 유지보수할 수 있습니다.

2. 기본 문법

변수와 타입

package main

func main() {

// 명시적 선언

var name string = "Gopher"

var age int = 10

// 짧은 선언 (타입 추론)

language := "Go"

version := 1.22

// 상수

const pi = 3.14159

fmt.Printf("%s는 %d살이고 %s %.2f를 씁니다\n",

name, age, language, version)

fmt.Println("원주율:", pi)

}

함수

// 기본 함수

func add(a, b int) int {

return a + b

}

// 다중 반환값

func divide(a, b float64) (float64, error) {

if b == 0 {

return 0, fmt.Errorf("0으로 나눌 수 없습니다")

}

return a / b, nil

}

// 명명된 반환값

func swap(a, b string) (first, second string) {

first = b

second = a

return // naked return

}

// 가변 인자

func sum(nums ...int) int {

total := 0

for _, n := range nums {

total += n

}

return total

}

구조체

type User struct {

Name string

Email string

Age int

}

// 메서드 (값 리시버)

func (u User) String() string {

return fmt.Sprintf("%s (%s)", u.Name, u.Email)

}

// 메서드 (포인터 리시버 - 값 수정 가능)

func (u *User) SetEmail(email string) {

u.Email = email

}

func main() {

user := User{Name: "김영주", Email: "yj@example.com", Age: 30}

user.SetEmail("new@example.com")

fmt.Println(user) // 김영주 (new@example.com)

}

포인터

func main() {

x := 42

p := &x // x의 주소

fmt.Println(*p) // 42 (역참조)

*p = 100

fmt.Println(x) // 100

}

// Go에는 포인터 산술이 없습니다 - 안전합니다

// C와 달리 댕글링 포인터 걱정이 적습니다

슬라이스와 맵

func main() {

// 슬라이스

nums := []int{1, 2, 3, 4, 5}

nums = append(nums, 6, 7)

sub := nums[1:4] // [2, 3, 4]

// make로 생성

buffer := make([]byte, 0, 1024) // len=0, cap=1024

// 맵

scores := map[string]int{

"Alice": 95,

"Bob": 87,

}

scores["Charlie"] = 92

// 키 존재 확인

val, ok := scores["Dave"]

if !ok {

fmt.Println("Dave의 점수가 없습니다")

}

// 맵 순회

for name, score := range scores {

fmt.Printf("%s: %d\n", name, score)

}

}

3. 고루틴과 채널

Go의 동시성 모델은 **CSP(Communicating Sequential Processes)** 기반입니다. 핵심은 두 가지입니다: **고루틴**과 **채널**.

고루틴 기초

func worker(id int) {

fmt.Printf("Worker %d 시작\n", id)

time.Sleep(time.Second)

fmt.Printf("Worker %d 완료\n", id)

}

func main() {

for i := 1; i <= 5; i++ {

go worker(i) // 고루틴 실행

}

// 메인 고루틴이 끝나면 프로그램 종료

time.Sleep(2 * time.Second)

}

고루틴은 **OS 스레드가 아닙니다**. Go 런타임이 수천 개의 고루틴을 소수의 OS 스레드에 **다중화(multiplexing)** 합니다. 고루틴 하나의 초기 스택은 약 2KB로, 수십만 개를 동시에 실행할 수 있습니다.

채널 통신

func producer(ch chan<- int) {

for i := 0; i < 5; i++ {

ch <- i // 채널에 값 전송

fmt.Printf("전송: %d\n", i)

}

close(ch) // 채널 닫기

}

func consumer(ch <-chan int) {

for val := range ch { // 채널이 닫힐 때까지 수신

fmt.Printf("수신: %d\n", val)

}

}

func main() {

ch := make(chan int, 3) // 버퍼 크기 3

go producer(ch)

consumer(ch) // 메인 고루틴에서 소비

}

select 문

func main() {

ch1 := make(chan string)

ch2 := make(chan string)

go func() {

time.Sleep(1 * time.Second)

ch1 <- "one"

}()

go func() {

time.Sleep(2 * time.Second)

ch2 <- "two"

}()

for i := 0; i < 2; i++ {

select {

case msg := <-ch1:

fmt.Println("ch1:", msg)

case msg := <-ch2:

fmt.Println("ch2:", msg)

case <-time.After(3 * time.Second):

fmt.Println("타임아웃!")

}

}

}

WaitGroup

func main() {

var wg sync.WaitGroup

urls := []string{

"https://example.com",

"https://golang.org",

"https://github.com",

}

for _, url := range urls {

wg.Add(1)

go func(u string) {

defer wg.Done()

resp, err := http.Get(u)

if err != nil {

fmt.Printf("에러: %s - %v\n", u, err)

return

}

defer resp.Body.Close()

fmt.Printf("%s -> %s\n", u, resp.Status)

}(url)

}

wg.Wait() // 모든 고루틴 완료 대기

fmt.Println("모든 요청 완료")

}

데드락 방지 패턴

// 나쁜 예: 데드락 발생

func bad() {

ch := make(chan int) // 버퍼 없는 채널

ch <- 1 // 수신자가 없어서 영원히 블록

fmt.Println(<-ch)

}

// 좋은 예 1: 고루틴으로 전송

func good1() {

ch := make(chan int)

go func() { ch <- 1 }()

fmt.Println(<-ch)

}

// 좋은 예 2: 버퍼 채널 사용

func good2() {

ch := make(chan int, 1)

ch <- 1

fmt.Println(<-ch)

}

워커 풀 패턴

func workerPool(jobs <-chan int, results chan<- int, id int) {

for j := range jobs {

fmt.Printf("Worker %d 처리 중: job %d\n", id, j)

time.Sleep(time.Millisecond * 500)

results <- j * 2

}

}

func main() {

const numJobs = 10

const numWorkers = 3

jobs := make(chan int, numJobs)

results := make(chan int, numJobs)

// 워커 시작

for w := 1; w <= numWorkers; w++ {

go workerPool(jobs, results, w)

}

// 작업 전송

for j := 1; j <= numJobs; j++ {

jobs <- j

}

close(jobs)

// 결과 수집

for r := 1; r <= numJobs; r++ {

result := <-results

fmt.Printf("결과: %d\n", result)

}

}

4. 인터페이스

Go의 인터페이스는 **암시적으로 구현**됩니다. implements 키워드가 없습니다.

기본 인터페이스

type Shape interface {

Area() float64

Perimeter() float64

}

type Circle struct {

Radius float64

}

func (c Circle) Area() float64 {

return math.Pi * c.Radius * c.Radius

}

func (c Circle) Perimeter() float64 {

return 2 * math.Pi * c.Radius

}

type Rectangle struct {

Width, Height float64

}

func (r Rectangle) Area() float64 {

return r.Width * r.Height

}

func (r Rectangle) Perimeter() float64 {

return 2 * (r.Width + r.Height)

}

// Shape 인터페이스를 매개변수로 받는 함수

func printInfo(s Shape) {

fmt.Printf("면적: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())

}

io.Reader / io.Writer

Go 표준 라이브러리의 가장 강력한 인터페이스입니다.

// io.Reader 인터페이스

// type Reader interface {

// Read(p []byte) (n int, err error)

// }

// io.Writer 인터페이스

// type Writer interface {

// Write(p []byte) (n int, err error)

// }

func countBytes(r io.Reader) (int, error) {

buf := make([]byte, 1024)

total := 0

for {

n, err := r.Read(buf)

total += n

if err == io.EOF {

return total, nil

}

if err != nil {

return total, err

}

}

}

func main() {

// 문자열에서 읽기

r := strings.NewReader("Hello, Go!")

n, _ := countBytes(r)

fmt.Printf("%d 바이트\n", n) // 10 바이트

// 파일에서 읽기

f, _ := os.Open("data.txt")

defer f.Close()

n, _ = countBytes(f)

fmt.Printf("%d 바이트\n", n)

}

빈 인터페이스와 타입 단언

func describe(i interface{}) {

// 타입 단언

if s, ok := i.(string); ok {

fmt.Println("문자열:", s)

return

}

// 타입 스위치

switch v := i.(type) {

case int:

fmt.Println("정수:", v)

case float64:

fmt.Println("실수:", v)

case bool:

fmt.Println("불린:", v)

default:

fmt.Printf("알 수 없는 타입: %T\n", v)

}

}

func main() {

describe(42)

describe("hello")

describe(3.14)

describe(true)

}

인터페이스 조합

type Reader interface {

Read(p []byte) (n int, err error)

}

type Writer interface {

Write(p []byte) (n int, err error)

}

type Closer interface {

Close() error

}

// 인터페이스 조합 (embedding)

type ReadWriter interface {

Reader

Writer

}

type ReadWriteCloser interface {

Reader

Writer

Closer

}

5. 에러 처리

Go는 예외(exception) 대신 **명시적 에러 반환**을 사용합니다.

errors.New와 fmt.Errorf

"errors"

"fmt"

)

var ErrNotFound = errors.New("not found")

func findUser(id int) (*User, error) {

if id <= 0 {

return nil, fmt.Errorf("잘못된 사용자 ID: %d", id)

}

// ... DB 조회

return nil, ErrNotFound

}

에러 래핑과 errors.Is / errors.As

type ValidationError struct {

Field string

Message string

}

func (e *ValidationError) Error() string {

return fmt.Sprintf("유효성 검사 실패 - %s: %s", e.Field, e.Message)

}

func validateAge(age int) error {

if age < 0 || age > 150 {

return &ValidationError{

Field: "age",

Message: fmt.Sprintf("나이는 0-150 범위여야 합니다 (입력값: %d)", age),

}

}

return nil

}

func processUser(age int) error {

if err := validateAge(age); err != nil {

return fmt.Errorf("사용자 처리 실패: %w", err) // 에러 래핑

}

return nil

}

func main() {

err := processUser(-5)

// errors.Is: 에러 체인에서 특정 에러 확인

if errors.Is(err, ErrNotFound) {

fmt.Println("사용자를 찾을 수 없습니다")

}

// errors.As: 에러 체인에서 특정 타입으로 변환

var valErr *ValidationError

if errors.As(err, &valErr) {

fmt.Printf("필드: %s, 메시지: %s\n", valErr.Field, valErr.Message)

}

}

커스텀 에러 패턴

type AppError struct {

Code int

Message string

Err error

}

func (e *AppError) Error() string {

if e.Err != nil {

return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)

}

return fmt.Sprintf("[%d] %s", e.Code, e.Message)

}

func (e *AppError) Unwrap() error {

return e.Err

}

// 에러 생성 헬퍼

func NewAppError(code int, msg string, err error) *AppError {

return &AppError{Code: code, Message: msg, Err: err}

}

6. 제네릭 (Go 1.18+)

Go 1.18부터 제네릭이 도입되어 타입 안전한 범용 코드를 작성할 수 있습니다.

타입 파라미터

func Min[T constraints.Ordered](a, b T) T {

if a < b {

return a

}

return b

}

func main() {

fmt.Println(Min(3, 5)) // int

fmt.Println(Min(3.14, 2.71)) // float64

fmt.Println(Min("a", "b")) // string

}

제약 조건

// 커스텀 제약 조건

type Number interface {

constraints.Integer | constraints.Float

}

func Sum[T Number](nums []T) T {

var total T

for _, n := range nums {

total += n

}

return total

}

// 구조체에 제네릭 적용

type Stack[T any] struct {

items []T

}

func (s *Stack[T]) Push(item T) {

s.items = append(s.items, item)

}

func (s *Stack[T]) Pop() (T, bool) {

if len(s.items) == 0 {

var zero T

return zero, false

}

item := s.items[len(s.items)-1]

s.items = s.items[:len(s.items)-1]

return item, true

}

func (s *Stack[T]) Len() int {

return len(s.items)

}

실전 제네릭 예제: 맵 유틸리티

func Keys[K comparable, V any](m map[K]V) []K {

keys := make([]K, 0, len(m))

for k := range m {

keys = append(keys, k)

}

return keys

}

func Values[K comparable, V any](m map[K]V) []V {

vals := make([]V, 0, len(m))

for _, v := range m {

vals = append(vals, v)

}

return vals

}

func Filter[T any](slice []T, predicate func(T) bool) []T {

var result []T

for _, item := range slice {

if predicate(item) {

result = append(result, item)

}

}

return result

}

func Map[T any, U any](slice []T, transform func(T) U) []U {

result := make([]U, len(slice))

for i, item := range slice {

result[i] = transform(item)

}

return result

}

7. 테스트

Go는 내장 테스트 프레임워크가 강력합니다. 별도 라이브러리 없이도 충분합니다.

기본 테스트

// math.go

package math

func Add(a, b int) int {

return a + b

}

func Fibonacci(n int) int {

if n <= 1 {

return n

}

return Fibonacci(n-1) + Fibonacci(n-2)

}

// math_test.go

package math

func TestAdd(t *testing.T) {

result := Add(2, 3)

if result != 5 {

t.Errorf("Add(2, 3) = %d; want 5", result)

}

}

테이블 드리븐 테스트

func TestFibonacci(t *testing.T) {

tests := []struct {

name string

input int

expected int

}{

{"zero", 0, 0},

{"one", 1, 1},

{"two", 2, 1},

{"five", 5, 5},

{"ten", 10, 55},

}

for _, tc := range tests {

t.Run(tc.name, func(t *testing.T) {

result := Fibonacci(tc.input)

if result != tc.expected {

t.Errorf("Fibonacci(%d) = %d; want %d",

tc.input, result, tc.expected)

}

})

}

}

벤치마크

func BenchmarkFibonacci(b *testing.B) {

for i := 0; i < b.N; i++ {

Fibonacci(20)

}

}

// 실행: go test -bench=. -benchmem

// BenchmarkFibonacci-8 28735 41523 ns/op 0 B/op 0 allocs/op

httptest

func TestHealthHandler(t *testing.T) {

req := httptest.NewRequest("GET", "/health", nil)

w := httptest.NewRecorder()

healthHandler(w, req)

resp := w.Result()

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {

t.Errorf("상태 코드 = %d; want %d",

resp.StatusCode, http.StatusOK)

}

body, _ := io.ReadAll(resp.Body)

if string(body) != `{"status":"ok"}` {

t.Errorf("응답 = %s; want {\"status\":\"ok\"}", body)

}

}

8. 패키지와 모듈

go mod 초기화

새 프로젝트 시작

mkdir myproject && cd myproject

go mod init github.com/username/myproject

의존성 추가

go get github.com/gin-gonic/gin@latest

사용하지 않는 의존성 정리

go mod tidy

의존성 다운로드

go mod download

프로젝트 구조

myproject/

cmd/

server/

main.go # 엔트리포인트

internal/

handler/

user.go # HTTP 핸들러

user_test.go

service/

user.go # 비즈니스 로직

repository/

user.go # 데이터 접근

pkg/

validator/

validator.go # 외부 공개 유틸리티

go.mod

go.sum

internal 패키지

`internal` 디렉토리에 있는 패키지는 해당 모듈 외부에서 임포트할 수 없습니다. 이를 통해 공개 API와 내부 구현을 명확히 분리합니다.

// internal/config/config.go

package config

type Config struct {

Port int

DBHost string

LogLevel string

}

func Load() (*Config, error) {

// 환경 변수에서 설정 로드

return &Config{

Port: 8080,

DBHost: "localhost:5432",

LogLevel: "info",

}, nil

}

9. 실전 패턴

웹 서버 (net/http)

package main

"encoding/json"

"log"

"net/http"

"time"

)

type Response struct {

Message string `json:"message"`

Timestamp string `json:"timestamp"`

}

func healthHandler(w http.ResponseWriter, r *http.Request) {

w.Header().Set("Content-Type", "application/json")

json.NewEncoder(w).Encode(Response{

Message: "ok",

Timestamp: time.Now().Format(time.RFC3339),

})

}

func main() {

mux := http.NewServeMux()

mux.HandleFunc("GET /health", healthHandler)

mux.HandleFunc("GET /api/users", getUsersHandler)

server := &http.Server{

Addr: ":8080",

Handler: mux,

ReadTimeout: 10 * time.Second,

WriteTimeout: 10 * time.Second,

IdleTimeout: 60 * time.Second,

}

log.Printf("서버 시작: %s", server.Addr)

log.Fatal(server.ListenAndServe())

}

미들웨어

func loggingMiddleware(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

start := time.Now()

log.Printf("[%s] %s 시작", r.Method, r.URL.Path)

next.ServeHTTP(w, r)

log.Printf("[%s] %s 완료 (%v)",

r.Method, r.URL.Path, time.Since(start))

})

}

func authMiddleware(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

token := r.Header.Get("Authorization")

if token == "" {

http.Error(w, "인증 필요", http.StatusUnauthorized)

return

}

// 토큰 검증 로직...

next.ServeHTTP(w, r)

})

}

// 미들웨어 체이닝

func chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {

for i := len(middlewares) - 1; i >= 0; i-- {

h = middlewares[i](h)

}

return h

}

func main() {

mux := http.NewServeMux()

mux.HandleFunc("GET /health", healthHandler)

handler := chain(mux, loggingMiddleware, authMiddleware)

log.Fatal(http.ListenAndServe(":8080", handler))

}

Graceful Shutdown

func main() {

mux := http.NewServeMux()

mux.HandleFunc("GET /health", healthHandler)

server := &http.Server{

Addr: ":8080",

Handler: mux,

}

// 서버를 고루틴에서 시작

go func() {

log.Println("서버 시작: :8080")

if err := server.ListenAndServe(); err != http.ErrServerClosed {

log.Fatalf("서버 에러: %v", err)

}

}()

// 종료 시그널 대기

quit := make(chan os.Signal, 1)

signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

<-quit

log.Println("서버 종료 시작...")

// 30초 타임아웃으로 graceful shutdown

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)

defer cancel()

if err := server.Shutdown(ctx); err != nil {

log.Fatalf("서버 강제 종료: %v", err)

}

log.Println("서버 종료 완료")

}

설정 관리

type Config struct {

Server ServerConfig

Database DatabaseConfig

}

type ServerConfig struct {

Port int `json:"port"`

ReadTimeout time.Duration `json:"read_timeout"`

WriteTimeout time.Duration `json:"write_timeout"`

}

type DatabaseConfig struct {

Host string `json:"host"`

Port int `json:"port"`

User string `json:"user"`

Password string `json:"password"`

DBName string `json:"dbname"`

}

func (d DatabaseConfig) DSN() string {

return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",

d.Host, d.Port, d.User, d.Password, d.DBName)

}

func LoadConfig(path string) (*Config, error) {

data, err := os.ReadFile(path)

if err != nil {

return nil, fmt.Errorf("설정 파일 읽기 실패: %w", err)

}

var cfg Config

if err := json.Unmarshal(data, &cfg); err != nil {

return nil, fmt.Errorf("설정 파싱 실패: %w", err)

}

return &cfg, nil

}

10. Go in 2026

클라우드 인프라

Go는 클라우드 네이티브 생태계의 **사실상 표준 언어**입니다.

- **컨테이너 오케스트레이션**: Kubernetes, Docker, containerd

- **서비스 메시**: Istio, Linkerd

- **모니터링**: Prometheus, Grafana Agent, Thanos

- **IaC**: Terraform, Pulumi

- **CI/CD**: Drone, Tekton

CLI 도구

Go로 만든 CLI 도구는 단일 바이너리로 배포되어 설치가 간편합니다.

// cobra를 이용한 CLI 예제

package main

"fmt"

"os"

"github.com/spf13/cobra"

)

var rootCmd = &cobra.Command{

Use: "mytool",

Short: "나의 CLI 도구",

}

var greetCmd = &cobra.Command{

Use: "greet [name]",

Short: "인사하기",

Args: cobra.ExactArgs(1),

Run: func(cmd *cobra.Command, args []string) {

fmt.Printf("안녕하세요, %s!\n", args[0])

},

}

func main() {

rootCmd.AddCommand(greetCmd)

if err := rootCmd.Execute(); err != nil {

os.Exit(1)

}

}

마이크로서비스

Go는 마이크로서비스 아키텍처에 최적화되어 있습니다.

- **빠른 시작 시간**: 콜드 스타트 지연 최소화

- **낮은 메모리 사용량**: 컨테이너 리소스 절약

- **정적 바이너리**: 경량 Docker 이미지 (scratch 베이스 가능)

- **gRPC 네이티브 지원**: protobuf + gRPC로 서비스 간 통신

- **OpenTelemetry**: 분산 추적 및 메트릭 수집

멀티 스테이지 빌드

FROM golang:1.22-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 go build -o /server ./cmd/server

FROM scratch

COPY --from=builder /server /server

EXPOSE 8080

ENTRYPOINT ["/server"]

최종 이미지 크기는 **10-20MB** 수준으로, Java나 Python 기반 서비스 대비 1/10 이하입니다.

마치며

Go는 **단순함이 곧 강력함**인 언어입니다. 복잡한 기능을 배제하고, 꼭 필요한 것만 제공합니다. 이 철학이 Docker, Kubernetes 같은 인프라 도구부터 CLI, 마이크로서비스까지 광범위한 영역에서 Go가 선택받는 이유입니다.

시작하기에 가장 좋은 방법은 직접 코드를 작성하는 것입니다. 간단한 CLI 도구나 REST API 서버를 만들어 보세요.

**Q1.** 고루틴과 OS 스레드의 차이는 무엇인가요?

Go 런타임이 고루틴을 소수의 OS 스레드에 다중화합니다. 고루틴의 초기 스택은 약 2KB로, OS 스레드의 1-8MB보다 훨씬 가볍습니다.

**Q2.** 버퍼 없는 채널에서 전송과 수신의 관계는?

전송자는 수신자가 준비될 때까지 블록되고, 수신자도 전송자가 준비될 때까지 블록됩니다. 동기적 통신 방식입니다.

**Q3.** select 문의 역할은?

여러 채널 연산을 동시에 대기하면서, 준비된 채널에서 먼저 처리합니다. 타임아웃이나 취소 처리에 유용합니다.

현재 단락 (1/744)

Go(Golang)는 2009년 Google에서 만든 언어입니다. 설계 철학은 **단순함, 빠른 컴파일, 강력한 동시성**입니다.

작성 글자: 0원문 글자: 14,231작성 단락: 0/744