1. Go란?
Go(Golang)는 2009년 Google이 공개한 오픈소스 프로그래밍 언어입니다. 동시성 처리에 최적화되어 있으며, 클라우드 인프라와 마이크로서비스 개발의 사실상 표준 언어로 자리 잡았습니다.
2. Go 설치 및 개발 환경 구축
Go 언어를 로컬 환경에서 실행하고 개발하기 위해서는 Go SDK 컴파일러와 효율적인 코드 작성을 도와줄 에디터(VS Code 등)가 필요합니다. 각 운영체제별로 가장 널리 사용되는 환경을 구축해보겠습니다.
2.1 운영체제별 Go SDK 설치
🪟 Windows 설치
Windows 환경에서는 MSI 인스톨러를 다운로드하여 클릭 몇 번으로 쉽게 설치할 수 있습니다.
- Go 공식 웹사이트 다운로드: Go 공식 다운로드 페이지에 접속하여 Windows용 최신 MSI 인스톨러(
.msi)를 다운로드합니다. - 설치 진행: 다운로드한 파일을 실행하고 기본 설정 경로(
C:\Go)로 설치를 완료합니다. - 환경 변수 확인: Go 인스톨러는 자동으로 시스템 환경 변수(
Path)에C:\Go\bin를 추가합니다. 터미널(cmd 또는 PowerShell)을 열고 아래 명령어로 설치 상태를 확인합니다.go versionbash
🍎 macOS 설치
macOS에서는 패키지 매니저인 Homebrew를 통해 한 줄로 완벽히 설치하거나, 공식 웹사이트에서 패키지 파일(.pkg)을 설치할 수 있습니다.
방법 A: Homebrew 설치 (추천)
brew install gobash
방법 B: 공식 패키지 설치
공식 웹사이트에서 macOS용 .pkg 인스톨러를 다운로드하여 마법사를 통해 설치합니다. (Apple Silicon용 M1/M2/M3 또는 Intel용 버전을 맞춰서 다운로드해야 합니다.)
설치가 완료되면 go version 명령어로 버전을 확인합니다.
🐧 Linux 설치
Ubuntu나 Debian 환경에서는 공식 tarball 아카이브를 사용해 다운로드하고 압축을 풀거나, 패키지 매니저를 통해 손쉽게 설치할 수 있습니다.
# Ubuntu / Debian 패키지 설치
sudo apt update
sudo apt install golang-go
# 설치 확인
go versionbash
2.2 VS Code에서 Go 개발 환경 및 모듈 구성
Visual Studio Code에서 Go 언어를 효율적으로 작성하고 도구들을 활용하기 위해 Google 공식 Go 확장을 셋업합니다.
- VS Code Go 확장 설치: VS Code 실행 후
Ctrl + Shift + X(macOS는Cmd + Shift + X)를 누른 뒤 Go (Go Team at Google) 공식 확장을 찾아 설치합니다. - Go 개발 도구(Tools) 일괄 설치:
확장 설치 후
Ctrl + Shift + P(macOS는Cmd + Shift + P)를 누르고 "Go: Install/Update Tools"를 검색해 실행합니다. 목록의 모든 도구(gopls, dlve, staticcheck 등)를 체크하고 OK를 클릭하면 컴파일 및 디버깅을 돕는 유틸리티가 일괄 설치됩니다. - Go Modules 프로젝트 초기화: Go 1.11 버전 이후에는 모듈 시스템(
go.mod)이 표준입니다.새 폴더를 열고 통합 터미널(
Ctrl + `)에서 아래 명령어를 수행해 프로젝트를 초기화합니다.go mod init my-go-projectbash - Go 코드 실행 및 빌드:
main.go를 만들고 소스코드를 작성한 후 터미널에서 다음 명령어로 즉시 실행하거나 빌드합니다.# 1. 컴파일 없이 즉시 코드 실행하기 go run main.go # 2. 실행 가능한 바이너리 파일로 빌드하기 go build main.go # 3. 빌드된 실행 파일 실행하기 # Windows: .\main.exe # macOS & Linux: ./mainbash
3. Hello World
Go 프로그램의 진입점은 항상 package main과 func main()입니다. 패키지를 임포트하여 표준 출력을 수행하는 첫 코드를 작성해 봅니다.
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}go
4. 변수와 타입 (Variables & Types)
Go는 정적 타입 언어로, 변수 선언 시 명시적으로 타입을 선언하거나 선언과 동시에 초기화할 때 타입을 생략할 수 있습니다. 특히 함수 내부에서는 간축 선언 연산자(:=)를 널리 사용합니다.
package main
import "fmt"
func main() {
// 1. var 키워드를 이용한 선언
var age int = 25
var name string = "Gopher"
// 2. := 단축 변수 선언 (함수 내부에서만 가능하며 타입 추론 적용)
score := 95.5 // float64로 자동 매핑
isPass := true // bool로 자동 매핑
fmt.Printf("이름: %s, 나이: %d, 점수: %f, 합격: %t\n", name, age, score, isPass)
}go
5. 제어문 (Control Flow)
Go의 제어문(if, for, switch)은 괄호(())를 사용하지 않는다는 점이 특징이며, 조건문 블록을 여는 중괄호({})는 반드시 한 줄에 위치해야 합니다. 또한 Go에는 while 키워드가 없으며 오직 for 키워드 하나로 모든 반복 동작을 제어합니다.
package main
import "fmt"
func main() {
// 1. if-else (괄호 없음, 중괄호 필수)
num := 10
if num%2 == 0 {
fmt.Println("짝수")
} else {
fmt.Println("홀수")
}
// 2. for 반복문 (일반적인 루프)
for i := 0; i < 3; i++ {
fmt.Println(i)
}
// 3. while 형태를 흉내낸 for 루프 (조건식만 기재)
n := 1
for n < 10 {
n *= 2
}
}go
6. 함수 (Functions)
Go의 함수는 일급 객체입니다. 다중 반환값, 이름 있는 반환값, 클로저, defer, panic/recover를 지원합니다.
package main
import (
"errors"
"fmt"
)
// 다중 반환값 + 이름 있는 반환값
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = errors.New("0으로 나눌 수 없습니다")
return // named return: result=0, err=에러
}
result = a / b
return
}
// 가변 인자
func sum(nums ...int) int {
total := 0
for _, n := range nums { total += n }
return total
}
// 함수를 값으로 — 고차 함수
func apply(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums { result[i] = fn(n) }
return result
}
func main() {
res, err := divide(10, 3)
if err != nil { fmt.Println(err); return }
fmt.Printf("%.4f\n", res) // 3.3333
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
doubled := apply([]int{1, 2, 3}, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6]
}go
클로저 & defer & panic/recover
// 클로저: 외부 변수를 캡처
func counter(start int) func() int {
count := start
return func() int {
count++
return count
}
}
// defer: 함수 종료 시 실행 (역순 LIFO)
func readFile(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close() // 함수 종료 시 자동 실행
defer fmt.Println("파일 처리 완료") // 나중에 defer된 게 먼저 실행
// ... 파일 처리 ...
return nil
}
// panic/recover: 예상치 못한 오류 복구
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("패닉 복구: %v", r)
}
}()
return a / b, nil // b=0이면 panic
}
func main() {
c := counter(0)
fmt.Println(c(), c(), c()) // 1 2 3
res, err := safeDiv(10, 0)
fmt.Println(res, err) // 0 패닉 복구: runtime error: ...
}go
7. 배열, 슬라이스, 맵 (Arrays, Slices, Maps)
Go에서 슬라이스(Slice)는 크기가 고정된 일반 배열과 달리 크기를 동적으로 늘릴 수 있는 초강력 핵심 가변 자료형입니다. 맵(Map)은 해시 테이블 구조의 키-값 쌍을 갖는 구조입니다.
package main
import "fmt"
func main() {
// 1. 배열 (크기가 고정)
var arr [3]int = [3]int{1, 2, 3}
// 2. 슬라이스 (크기 선언 없이 생성하며 append로 확장 가능)
slice := []int{10, 20}
slice = append(slice, 30, 40) // 동적 추가
// 3. 맵 (make를 이용해 초기화)
grades := make(map[string]int)
grades["철수"] = 90
grades["영희"] = 95
fmt.Println(arr, slice, grades["철수"])
}go
8. 구조체와 메서드 (Structs & Methods)
Go는 클래스 키워드를 지원하지 않으며 struct(구조체)를 통해 사용자 정의 타입을 선언하고, 특정 리시버 함수인 **메서드(Method)**를 통해 타입 내부의 동작을 결합합니다.
package main
import "fmt"
// 구조체 선언
type Rectangle struct {
width, height float64
}
// 리시버 메서드 정의 (Rectangle 구조체에 Area 기능 매핑)
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func main() {
rect := Rectangle{width: 10, height: 5}
fmt.Println("사각형 면적:", rect.Area())
}go
9. 인터페이스 (Interfaces)
Go 인터페이스는 암시적 구현입니다. implements 없이 메서드만 구현하면 됩니다. 표준 라이브러리의 핵심 패턴입니다.
package main
import (
"fmt"
"math"
)
// 인터페이스 정의
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
// 인터페이스로 다형성 처리
func printInfo(s Shape) {
fmt.Printf("넓이: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}
// 빈 인터페이스 — 어떤 타입이든 수용
func describe(v any) { // any = interface{}
fmt.Printf("값: %v, 타입: %T\n", v, v)
}
// 타입 단언 & 타입 스위치
func process(v any) {
switch t := v.(type) {
case int: fmt.Printf("정수: %d\n", t)
case string: fmt.Printf("문자열: %s (길이 %d)\n", t, len(t))
case Shape: fmt.Printf("도형 넓이: %.2f\n", t.Area())
default: fmt.Printf("알 수 없는 타입: %T\n", t)
}
}
func main() {
shapes := []Shape{Circle{5}, Rectangle{3, 4}}
for _, s := range shapes { printInfo(s) }
}go
인터페이스 임베딩 & io 표준 패턴
// 인터페이스 임베딩으로 조합
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface {
Reader
Writer
}
// 표준 라이브러리 io.Reader 구현 예시
type StringReader struct {
data string
pos int
}
func (r *StringReader) Read(p []byte) (int, error) {
if r.pos >= len(r.data) {
return 0, io.EOF
}
n := copy(p, r.data[r.pos:])
r.pos += n
return n, nil
}
// fmt.Stringer 인터페이스 — fmt.Println에서 자동 호출
type Point struct{ X, Y int }
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}
p := Point{3, 4}
fmt.Println(p) // (3, 4)go
10. 에러 처리 (Error Handling)
Go는 예외(try-catch) 시스템을 일절 지원하지 않는 대신, 함수가 최종 결과값과 함께 error 인터페이스를 직접 수신받아 명시적으로 if err != nil 패턴을 거쳐 예외 상태를 안전히 통제합니다.
package main
import (
"errors"
"fmt"
)
func validateAge(age int) (string, error) {
if age < 0 {
return "", errors.New("나이는 음수가 될 수 없습니다")
}
return "유효한 나이", nil
}
func main() {
res, err := validateAge(-5)
// Go의 가장 표준적이고 직관적인 에러 검출 정석 양식
if err != nil {
fmt.Println("에러 확인:", err)
return
}
fmt.Println(res)
}go
11. 고루틴과 채널 (Goroutines & Channels)
고루틴은 Go 런타임이 관리하는 경량 스레드입니다. 수십만 개를 동시에 실행할 수 있으며, 채널로 안전하게 통신합니다.
package main
import (
"context"
"fmt"
"sync"
"time"
)
// ── WaitGroup: 완료 대기 ─────────────────────────
func withWaitGroup() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d 시작\n", id)
time.Sleep(time.Duration(id*10) * time.Millisecond)
fmt.Printf("Worker %d 완료\n", id)
}(i)
}
wg.Wait()
fmt.Println("모든 고루틴 완료")
}
// ── 버퍼드 채널 ──────────────────────────────────
func bufferedChannel() {
ch := make(chan int, 3) // 버퍼 크기 3 (블로킹 없이 3개까지 전송)
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 // 블로킹 (버퍼 가득참)
fmt.Println(<-ch, <-ch, <-ch) // 1 2 3
}
// ── select: 여러 채널 동시 대기 ──────────────────
func withSelect() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { time.Sleep(50*time.Millisecond); ch1 <- "채널1" }()
go func() { time.Sleep(30*time.Millisecond); ch2 <- "채널2" }()
select {
case msg := <-ch1: fmt.Println("받음:", msg)
case msg := <-ch2: fmt.Println("받음:", msg) // 더 빠름
case <-time.After(1 * time.Second): fmt.Println("타임아웃")
}
}
// ── Context: 취소 전파 ────────────────────────────
func withContext() {
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
ch := make(chan string, 1)
go func() {
time.Sleep(100 * time.Millisecond)
ch <- "결과"
}()
select {
case result := <-ch:
fmt.Println("성공:", result)
case <-ctx.Done():
fmt.Println("취소:", ctx.Err())
}
}
// ── sync.Mutex: 경쟁 조건 방지 ───────────────────
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func main() {
withWaitGroup()
withSelect()
withContext()
}go
12. 패키지 관리 (Package Management)
모던 Go 프로젝트는 패키지 종속성 도구인 `go modules`를 기본 채택하여 개발을 수행합니다.
# 1. 신규 모듈 초기화 (go.mod 빌드)
go mod init my-project
# 2. 외부 종속성 패키지 다운로드 및 캐싱
go get github.com/gin-gonic/gin
# 3. 미사용 모듈 제거 및 소스코드 분석 정리
go mod tidybash
13. 다음 단계 (Next Steps)
Go의 고성능 컴파일 동시성 패러다임을 이해하셨다면, 백엔드 고성능 웹 프레임워크인 **Gin, Fiber** 셋업에 돌입하시거나 REST API 개발 및 클라우드 마이크로서비스 도구에 도전하세요!
연계 가이드: 🍸 Gin 웹 프레임워크 가이드 · Docker 가이드 · Kubernetes 가이드