Go 언어 예제 100문항
메타데이터
🎯 학습 목표
- Go 언어 기초 문법을 실습으로 체화
- 파이썬 경험자가 빠르게 Go 패턴 익히기
- MIT 6.824 Lab 준비를 위한 핵심 개념 마스터
📚 문제 구성
- Level 1 (1-30): 기초 문법
- Level 2 (31-60): 함수, 구조체, 인터페이스
- Level 3 (61-85): 고루틴, 채널
- Level 4 (86-100): 네트워크, 실전 응용
Level 1: 기초 문법 (1-30)
1-5: 변수와 타입
// 1. 정수형 변수 선언하고 값 할당
package main
import "fmt"
func main() {
// 여기에 코드 작성
}문제 1: age 변수를 선언하고 25를 할당한 후 출력하세요.
// 답안
var age int = 25
fmt.Println(age)문제 2: 짧은 선언문으로 name에 “Alice” 할당하고 출력하세요.
// 답안
name := "Alice"
fmt.Println(name)문제 3: float64 타입으로 원주율 값(3.14159)을 저장하고 출력하세요.
// 답안
var pi float64 = 3.14159
fmt.Println(pi)문제 4: 여러 변수를 한 번에 선언하세요. (이름, 나이, 키)
// 답안
var name, age, height = "Bob", 30, 175.5
fmt.Println(name, age, height)문제 5: 상수를 선언하고 사용하세요. (원의 반지름 5)
// 답안
const radius = 5
area := 3.14 * radius * radius
fmt.Println("원의 넓이:", area)6-10: 조건문과 반복문
문제 6: 숫자가 짝수인지 홀수인지 판별하세요.
// 답안
num := 7
if num%2 == 0 {
fmt.Println("짝수")
} else {
fmt.Println("홀수")
}문제 7: 1부터 10까지 for문으로 출력하세요.
// 답안
for i := 1; i <= 10; i++ {
fmt.Println(i)
}문제 8: switch문으로 요일 판별하세요.
// 답안
day := 3
switch day {
case 1:
fmt.Println("월요일")
case 2:
fmt.Println("화요일")
case 3:
fmt.Println("수요일")
default:
fmt.Println("기타")
}문제 9: while 대신 for문으로 무한루프 만들고 조건으로 빠져나오기
// 답안
count := 0
for {
count++
if count > 5 {
break
}
fmt.Println(count)
}문제 10: range를 사용해서 문자열의 각 문자 출력하기
// 답안
text := "Hello"
for i, char := range text {
fmt.Printf("%d: %c\n", i, char)
}11-20: 배열과 슬라이스
문제 11: 길이 5인 정수 배열 선언하고 초기화하세요.
// 답안
var numbers [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println(numbers)문제 12: 슬라이스 만들고 요소 추가하기
// 답안
fruits := []string{"apple", "banana"}
fruits = append(fruits, "orange")
fmt.Println(fruits)문제 13: 슬라이스에서 특정 범위 잘라내기
// 답안
nums := []int{1, 2, 3, 4, 5, 6}
slice := nums[2:5] // [3, 4, 5]
fmt.Println(slice)문제 14: 슬라이스 길이와 용량 출력하기
// 답안
s := make([]int, 5, 10)
fmt.Printf("길이: %d, 용량: %d\n", len(s), cap(s))문제 15: 2차원 슬라이스 만들기 (3x3 행렬)
// 답안
matrix := make([][]int, 3)
for i := range matrix {
matrix[i] = make([]int, 3)
}
fmt.Println(matrix)문제 16: 슬라이스에서 최대값 찾기
// 답안
nums := []int{3, 7, 2, 9, 1}
max := nums[0]
for _, num := range nums {
if num > max {
max = num
}
}
fmt.Println("최대값:", max)문제 17: 슬라이스 정렬하기 (sort 패키지 사용)
// 답안
import "sort"
nums := []int{3, 1, 4, 1, 5}
sort.Ints(nums)
fmt.Println(nums)문제 18: 문자열 슬라이스에서 특정 문자열 찾기
// 답안
names := []string{"Alice", "Bob", "Charlie"}
target := "Bob"
for i, name := range names {
if name == target {
fmt.Printf("%s found at index %d\n", target, i)
break
}
}문제 19: 슬라이스 복사하기
// 답안
original := []int{1, 2, 3}
copied := make([]int, len(original))
copy(copied, original)
fmt.Println("원본:", original, "복사:", copied)문제 20: 슬라이스에서 중복 제거하기
// 답안
nums := []int{1, 2, 2, 3, 3, 3, 4}
unique := []int{}
for _, num := range nums {
found := false
for _, u := range unique {
if u == num {
found = true
break
}
}
if !found {
unique = append(unique, num)
}
}
fmt.Println(unique)21-30: 맵(Map)
문제 21: 맵 선언하고 값 할당하기
// 답안
scores := make(map[string]int)
scores["Alice"] = 95
scores["Bob"] = 87
fmt.Println(scores)문제 22: 맵 리터럴로 초기화하기
// 답안
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
fmt.Println(colors)문제 23: 맵에서 키 존재 확인하기
// 답안
scores := map[string]int{"Alice": 95, "Bob": 87}
if score, exists := scores["Charlie"]; exists {
fmt.Println("Charlie의 점수:", score)
} else {
fmt.Println("Charlie의 점수를 찾을 수 없습니다")
}문제 24: 맵 순회하기
// 답안
ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 35}
for name, age := range ages {
fmt.Printf("%s은 %d살입니다\n", name, age)
}문제 25: 맵에서 키 삭제하기
// 답안
m := map[string]int{"a": 1, "b": 2, "c": 3}
delete(m, "b")
fmt.Println(m)문제 26: 맵의 모든 키 추출하기
// 답안
m := map[string]int{"apple": 5, "banana": 3, "orange": 8}
keys := []string{}
for key := range m {
keys = append(keys, key)
}
fmt.Println("키들:", keys)문제 27: 맵의 모든 값 합계 구하기
// 답안
prices := map[string]int{"apple": 1000, "banana": 500, "orange": 800}
total := 0
for _, price := range prices {
total += price
}
fmt.Println("총 가격:", total)문제 28: 두 맵 합치기
// 답안
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"c": 3, "d": 4}
for k, v := range m2 {
m1[k] = v
}
fmt.Println(m1)문제 29: 맵에서 최대값을 가진 키 찾기
// 답안
scores := map[string]int{"Alice": 95, "Bob": 87, "Charlie": 92}
maxScore := 0
maxName := ""
for name, score := range scores {
if score > maxScore {
maxScore = score
maxName = name
}
}
fmt.Printf("최고 점수: %s (%d점)\n", maxName, maxScore)문제 30: 맵 값들로 슬라이스 만들기
// 답안
ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 35}
ageList := []int{}
for _, age := range ages {
ageList = append(ageList, age)
}
fmt.Println("나이들:", ageList)Level 2: 함수, 구조체, 인터페이스 (31-60)
31-40: 함수
문제 31: 두 수를 더하는 함수 만들기
// 답안
func add(a, b int) int {
return a + b
}
func main() {
result := add(3, 5)
fmt.Println(result)
}문제 32: 다중 반환값을 가진 함수 (몫과 나머지)
// 답안
func divMod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
quot, rem := divMod(17, 5)
fmt.Printf("몫: %d, 나머지: %d\n", quot, rem)
}문제 33: 가변 인자 함수로 여러 수의 합 구하기
// 답안
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func main() {
result := sum(1, 2, 3, 4, 5)
fmt.Println(result)
}문제 34: 함수를 매개변수로 받는 함수
// 답안
func apply(fn func(int) int, value int) int {
return fn(value)
}
func square(x int) int {
return x * x
}
func main() {
result := apply(square, 5)
fmt.Println(result) // 25
}문제 35: 클로저 함수 만들기 (카운터)
// 답안
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
}문제 36: 재귀함수로 팩토리얼 구하기
// 답안
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
func main() {
fmt.Println(factorial(5)) // 120
}문제 37: defer를 사용한 함수
// 답안
func example() {
defer fmt.Println("마지막에 실행됨")
fmt.Println("첫 번째")
fmt.Println("두 번째")
}
func main() {
example()
}문제 38: 에러 반환하는 함수
// 답안
import "errors"
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("0으로 나눌 수 없습니다")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("에러:", err)
return
}
fmt.Println("결과:", result)
}문제 39: 명명된 반환값 사용하기
// 답안
func calculate(a, b int) (sum, product int) {
sum = a + b
product = a * b
return // 명시적으로 sum, product 반환
}
func main() {
s, p := calculate(3, 4)
fmt.Printf("합: %d, 곱: %d\n", s, p)
}문제 40: 함수 타입을 변수로 사용하기
// 답안
type operation func(int, int) int
func add(a, b int) int { return a + b }
func multiply(a, b int) int { return a * b }
func main() {
var op operation
op = add
fmt.Println(op(3, 4)) // 7
op = multiply
fmt.Println(op(3, 4)) // 12
}41-50: 구조체
문제 41: 기본 구조체 정의하고 사용하기
// 답안
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 25}
fmt.Printf("%+v\n", p)
}문제 42: 구조체 포인터 사용하기
// 답안
type Point struct {
X, Y int
}
func main() {
p := &Point{X: 1, Y: 2}
fmt.Printf("X: %d, Y: %d\n", p.X, p.Y)
}문제 43: 구조체에 메서드 추가하기
// 답안
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
rect := Rectangle{Width: 5, Height: 3}
fmt.Println("넓이:", rect.Area())
}문제 44: 포인터 리시버 메서드
// 답안
type Counter struct {
Value int
}
func (c *Counter) Increment() {
c.Value++
}
func main() {
counter := Counter{Value: 0}
counter.Increment()
fmt.Println(counter.Value) // 1
}문제 45: 구조체 임베딩 (상속)
// 답안
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "동물이 소리를 냅니다"
}
type Dog struct {
Animal
Breed string
}
func main() {
dog := Dog{
Animal: Animal{Name: "멍멍이"},
Breed: "골든 리트리버",
}
fmt.Println(dog.Name) // Animal의 필드
fmt.Println(dog.Speak()) // Animal의 메서드
}문제 46: 구조체 슬라이스 정렬하기
// 답안
import "sort"
type Student struct {
Name string
Score int
}
type ByScore []Student
func (s ByScore) Len() int { return len(s) }
func (s ByScore) Less(i, j int) bool { return s[i].Score > s[j].Score }
func (s ByScore) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
students := []Student{
{"Alice", 85},
{"Bob", 95},
{"Charlie", 78},
}
sort.Sort(ByScore(students))
fmt.Println(students)
}문제 47: JSON 마샬링/언마샬링
// 답안
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
// JSON으로 변환
jsonData, _ := json.Marshal(user)
fmt.Println(string(jsonData))
// JSON에서 구조체로 변환
var newUser User
json.Unmarshal(jsonData, &newUser)
fmt.Printf("%+v\n", newUser)
}문제 48: 구조체 생성자 패턴
// 답안
type BankAccount struct {
owner string
balance float64
}
func NewBankAccount(owner string, initialBalance float64) *BankAccount {
return &BankAccount{
owner: owner,
balance: initialBalance,
}
}
func (ba *BankAccount) GetBalance() float64 {
return ba.balance
}
func main() {
account := NewBankAccount("Alice", 1000.0)
fmt.Println("잔고:", account.GetBalance())
}문제 49: 익명 구조체 사용하기
// 답안
func main() {
person := struct {
Name string
Age int
}{
Name: "Bob",
Age: 30,
}
fmt.Printf("%+v\n", person)
}문제 50: 구조체 비교하기
// 답안
type Point struct {
X, Y int
}
func main() {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
p3 := Point{X: 2, Y: 3}
fmt.Println("p1 == p2:", p1 == p2) // true
fmt.Println("p1 == p3:", p1 == p3) // false
}51-60: 인터페이스
문제 51: 기본 인터페이스 정의하고 구현하기
// 답안
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
var s Shape = Circle{Radius: 5}
fmt.Println("넓이:", s.Area())
}문제 52: 여러 타입이 같은 인터페이스 구현하기
// 답안
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "멍멍!" }
type Cat struct{}
func (c Cat) Speak() string { return "야옹!" }
func makeNoise(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
makeNoise(Dog{}) // 멍멍!
makeNoise(Cat{}) // 야옹!
}문제 53: 빈 인터페이스 (interface{}) 사용하기
// 답안
func printAnything(value interface{}) {
fmt.Printf("값: %v, 타입: %T\n", value, value)
}
func main() {
printAnything(42)
printAnything("hello")
printAnything([]int{1, 2, 3})
}문제 54: 타입 단언 (Type Assertion) 사용하기
// 답안
func checkType(value interface{}) {
if str, ok := value.(string); ok {
fmt.Printf("문자열: %s (길이: %d)\n", str, len(str))
} else {
fmt.Printf("문자열이 아님: %T\n", value)
}
}
func main() {
checkType("hello")
checkType(42)
}문제 55: 타입 스위치 사용하기
// 답안
func describeType(value interface{}) {
switch v := value.(type) {
case string:
fmt.Printf("문자열: %s\n", v)
case int:
fmt.Printf("정수: %d\n", v)
case bool:
fmt.Printf("불린: %t\n", v)
default:
fmt.Printf("알 수 없는 타입: %T\n", v)
}
}
func main() {
describeType("hello")
describeType(42)
describeType(true)
describeType(3.14)
}문제 56: 인터페이스 합성
// 답안
type Reader interface {
Read() string
}
type Writer interface {
Write(string)
}
type ReadWriter interface {
Reader
Writer
}
type File struct {
content string
}
func (f *File) Read() string {
return f.content
}
func (f *File) Write(data string) {
f.content = data
}
func main() {
var rw ReadWriter = &File{}
rw.Write("Hello, World!")
fmt.Println(rw.Read())
}문제 57: 인터페이스 값 nil 체크
// 답안
type Printer interface {
Print()
}
type ConsolePrinter struct{}
func (cp ConsolePrinter) Print() { fmt.Println("콘솔 출력") }
func main() {
var p Printer
if p == nil {
fmt.Println("Printer가 nil입니다")
}
p = ConsolePrinter{}
if p != nil {
p.Print()
}
}문제 58: 인터페이스를 매개변수로 받는 함수
// 답안
type Comparable interface {
Compare(other Comparable) int
}
type Number struct {
Value int
}
func (n Number) Compare(other Comparable) int {
if otherNum, ok := other.(Number); ok {
if n.Value < otherNum.Value {
return -1
} else if n.Value > otherNum.Value {
return 1
}
return 0
}
return 0
}
func findMax(items []Comparable) Comparable {
if len(items) == 0 {
return nil
}
max := items[0]
for _, item := range items[1:] {
if item.Compare(max) > 0 {
max = item
}
}
return max
}
func main() {
numbers := []Comparable{Number{5}, Number{2}, Number{8}, Number{1}}
max := findMax(numbers)
fmt.Printf("최대값: %+v\n", max)
}문제 59: 인터페이스 슬라이스 다루기
// 답안
type Animal interface {
MakeSound() string
}
type Dog struct{ Name string }
func (d Dog) MakeSound() string { return "멍멍" }
type Cat struct{ Name string }
func (c Cat) MakeSound() string { return "야옹" }
func main() {
animals := []Animal{
Dog{Name: "멍멍이"},
Cat{Name: "야옹이"},
Dog{Name: "왈왈이"},
}
for _, animal := range animals {
fmt.Println(animal.MakeSound())
}
}문제 60: 메서드 셋과 인터페이스
// 답안
type Incrementer interface {
Increment()
GetValue() int
}
type Counter struct {
value int
}
func (c *Counter) Increment() {
c.value++
}
func (c *Counter) GetValue() int {
return c.value
}
func useIncrementer(inc Incrementer) {
inc.Increment()
fmt.Println("현재 값:", inc.GetValue())
}
func main() {
counter := &Counter{value: 0}
useIncrementer(counter)
}Level 3: 고루틴과 채널 (61-85)
61-70: 고루틴 기초
문제 61: 기본 고루틴 사용하기
// 답안
import "time"
func sayHello() {
fmt.Println("Hello from goroutine!")
}
func main() {
go sayHello()
time.Sleep(time.Second) // 고루틴이 실행될 때까지 기다림
fmt.Println("Main function ending")
}문제 62: 여러 고루틴 동시 실행
// 답안
func worker(id int) {
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d finished\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
}문제 63: sync.WaitGroup 사용하기
// 답안
import "sync"
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d working...\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}문제 64: 경쟁 상태(Race Condition) 예제
// 답안
var counter int
func increment(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
counter++ // 경쟁 상태 발생!
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Printf("Counter: %d\n", counter) // 예상: 2000, 실제: ?
}문제 65: Mutex로 경쟁 상태 해결하기
// 답안
var (
counter int
mutex sync.Mutex
)
func safeIncrement(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
mutex.Lock()
counter++
mutex.Unlock()
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go safeIncrement(&wg)
}
wg.Wait()
fmt.Printf("Safe Counter: %d\n", counter) // 정확히 2000
}문제 66: 고루틴에서 패닉 처리하기
// 답안
func riskyOperation(id int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Goroutine %d recovered from: %v\n", id, r)
}
}()
if id == 2 {
panic("Something went wrong!")
}
fmt.Printf("Goroutine %d completed successfully\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go riskyOperation(i)
}
time.Sleep(time.Second)
}문제 67: 고루틴 풀 패턴
// 답안
func workerPool(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- job * 2 // 작업 처리 (2배)
}
}
func main() {
const numWorkers = 3
const numJobs = 9
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 워커 시작
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go workerPool(jobs, results, &wg)
}
// 작업 전송
for i := 1; i <= numJobs; i++ {
jobs <- i
}
close(jobs)
// 워커 종료 대기
go func() {
wg.Wait()
close(results)
}()
// 결과 수집
for result := range results {
fmt.Printf("Result: %d\n", result)
}
}문제 68: 고루틴 리크 방지
// 답안
func leakyGoroutine() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "data" // 받을 고루틴이 없으면 영원히 블록됨
}()
// 1초 후 타임아웃
select {
case data := <-ch:
fmt.Println("Received:", data)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
return // 여기서 반환하면 ch <- "data"가 영원히 블록됨
}
}
func fixedGoroutine() {
ch := make(chan string, 1) // 버퍼드 채널 사용
go func() {
time.Sleep(2 * time.Second)
ch <- "data" // 버퍼가 있어서 블록되지 않음
}()
select {
case data := <-ch:
fmt.Println("Received:", data)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
}문제 69: 컨텍스트로 고루틴 취소하기
// 답안
import "context"
func cancellableWorker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d cancelled\n", id)
return
default:
fmt.Printf("Worker %d working...\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
for i := 1; i <= 3; i++ {
go cancellableWorker(ctx, i)
}
time.Sleep(2 * time.Second)
cancel() // 모든 워커 취소
time.Sleep(time.Second)
}문제 70: 고루틴으로 팬아웃/팬인 패턴
// 답안
func producer(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// 팬아웃
c := producer(2, 3, 4)
// 팬인
c1 := square(c)
c2 := square(c)
// 결과 수집
for n := range c1 {
fmt.Println(n)
}
for n := range c2 {
fmt.Println(n)
}
}71-80: 채널 활용
문제 71: 기본 채널 사용하기
// 답안
func main() {
ch := make(chan string)
go func() {
ch <- "Hello, Channel!"
}()
message := <-ch
fmt.Println(message)
}문제 72: 버퍼드 채널 사용하기
// 답안
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
// ch <- 3 // 이건 블록됨 (버퍼 크기 2)
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
}문제 73: 채널 닫기와 범위 루프
// 답안
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for num := range c {
fmt.Println(num)
}
}문제 74: select 문으로 다중 채널 처리
// 답안
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("Received", msg1)
case msg2 := <-c2:
fmt.Println("Received", msg2)
}
}
}문제 75: select와 default로 논블로킹 처리
// 답안
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No message received")
}
}문제 76: 타이머와 채널
// 답안
func main() {
timer1 := time.NewTimer(2 * time.Second)
<-timer1.C
fmt.Println("Timer 1 expired")
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
time.Sleep(2 * time.Second)
}문제 77: 티커(Ticker) 사용하기
// 답안
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
done <- true
fmt.Println("Ticker stopped")
}문제 78: 파이프라인 패턴
// 답안
func pipeline() {
// Stage 1: 숫자 생성
numbers := make(chan int)
go func() {
for i := 1; i <= 5; i++ {
numbers <- i
}
close(numbers)
}()
// Stage 2: 제곱 계산
squares := make(chan int)
go func() {
for n := range numbers {
squares <- n * n
}
close(squares)
}()
// Stage 3: 결과 출력
for s := range squares {
fmt.Println("Square:", s)
}
}
func main() {
pipeline()
}문제 79: 채널로 세마포어 구현하기
// 답안
type Semaphore chan struct{}
func NewSemaphore(maxConcurrency int) Semaphore {
return make(chan struct{}, maxConcurrency)
}
func (s Semaphore) Acquire() {
s <- struct{}{}
}
func (s Semaphore) Release() {
<-s
}
func worker(id int, sem Semaphore, wg *sync.WaitGroup) {
defer wg.Done()
sem.Acquire()
defer sem.Release()
fmt.Printf("Worker %d working...\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
sem := NewSemaphore(2) // 최대 2개 동시 실행
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, sem, &wg)
}
wg.Wait()
}문제 80: 채널로 브로드캐스트 구현하기
// 답안
type Broadcaster struct {
listeners []chan string
mutex sync.Mutex
}
func NewBroadcaster() *Broadcaster {
return &Broadcaster{
listeners: make([]chan string, 0),
}
}
func (b *Broadcaster) Subscribe() <-chan string {
b.mutex.Lock()
defer b.mutex.Unlock()
ch := make(chan string, 10)
b.listeners = append(b.listeners, ch)
return ch
}
func (b *Broadcaster) Broadcast(message string) {
b.mutex.Lock()
defer b.mutex.Unlock()
for _, listener := range b.listeners {
select {
case listener <- message:
default: // 버퍼가 가득 차면 스킵
}
}
}
func main() {
broadcaster := NewBroadcaster()
// 구독자들
sub1 := broadcaster.Subscribe()
sub2 := broadcaster.Subscribe()
go func() {
for msg := range sub1 {
fmt.Println("Sub1 received:", msg)
}
}()
go func() {
for msg := range sub2 {
fmt.Println("Sub2 received:", msg)
}
}()
broadcaster.Broadcast("Hello")
broadcaster.Broadcast("World")
time.Sleep(time.Second)
}81-85: 동시성 패턴
문제 81: Once를 사용한 초기화
// 답안
var once sync.Once
var instance *Singleton
type Singleton struct {
value string
}
func GetSingleton() *Singleton {
once.Do(func() {
fmt.Println("Creating singleton instance")
instance = &Singleton{value: "I'm a singleton"}
})
return instance
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
s := GetSingleton()
fmt.Printf("Goroutine %d got: %s\n", id, s.value)
}(i)
}
wg.Wait()
}문제 82: RWMutex 사용하기
// 답안
type SafeCounter struct {
mutex sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Get(key string) int {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.count[key]
}
func (c *SafeCounter) Set(key string, value int) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.count == nil {
c.count = make(map[string]int)
}
c.count[key] = value
}
func main() {
counter := &SafeCounter{}
var wg sync.WaitGroup
// 여러 고루틴에서 동시 읽기/쓰기
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
counter.Set(key, id*10)
fmt.Printf("Set %s = %d\n", key, id*10)
}(i)
}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id%3)
value := counter.Get(key)
fmt.Printf("Get %s = %d\n", key, value)
}(i)
}
wg.Wait()
}문제 83: 조건 변수(Cond) 사용하기
// 답안
func main() {
var mutex sync.Mutex
cond := sync.NewCond(&mutex)
ready := false
// 기다리는 고루틴들
for i := 1; i <= 3; i++ {
go func(id int) {
mutex.Lock()
for !ready {
fmt.Printf("Worker %d waiting...\n", id)
cond.Wait()
}
fmt.Printf("Worker %d starting work!\n", id)
mutex.Unlock()
}(i)
}
time.Sleep(2 * time.Second)
// 신호 보내기
mutex.Lock()
ready = true
mutex.Unlock()
cond.Broadcast() // 모든 대기 중인 고루틴에게 신호
time.Sleep(time.Second)
}문제 84: 원자적 연산 사용하기
// 답안
import "sync/atomic"
var counter int64
func increment(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
atomic.AddInt64(&counter, 1)
}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Printf("Atomic counter: %d\n", atomic.LoadInt64(&counter))
}문제 85: 고루틴 풀을 이용한 작업 스케줄러
// 답안
type Job func()
type WorkerPool struct {
jobQueue chan Job
workerQueue chan chan Job
workers []*Worker
quit chan bool
}
type Worker struct {
id int
jobChannel chan Job
workerQueue chan chan Job
quit chan bool
}
func NewWorker(id int, workerQueue chan chan Job) *Worker {
return &Worker{
id: id,
jobChannel: make(chan Job),
workerQueue: workerQueue,
quit: make(chan bool),
}
}
func (w *Worker) Start() {
go func() {
for {
w.workerQueue <- w.jobChannel
select {
case job := <-w.jobChannel:
job()
case <-w.quit:
return
}
}
}()
}
func NewWorkerPool(maxWorkers int, maxQueue int) *WorkerPool {
pool := &WorkerPool{
jobQueue: make(chan Job, maxQueue),
workerQueue: make(chan chan Job, maxWorkers),
workers: make([]*Worker, maxWorkers),
quit: make(chan bool),
}
for i := 0; i < maxWorkers; i++ {
worker := NewWorker(i+1, pool.workerQueue)
pool.workers[i] = worker
worker.Start()
}
go pool.dispatch()
return pool
}
func (p *WorkerPool) dispatch() {
for {
select {
case job := <-p.jobQueue:
go func() {
workerJobChannel := <-p.workerQueue
workerJobChannel <- job
}()
case <-p.quit:
return
}
}
}
func (p *WorkerPool) Submit(job Job) {
p.jobQueue <- job
}
func main() {
pool := NewWorkerPool(3, 10)
for i := 1; i <= 10; i++ {
jobID := i
pool.Submit(func() {
fmt.Printf("Job %d started\n", jobID)
time.Sleep(time.Second)
fmt.Printf("Job %d completed\n", jobID)
})
}
time.Sleep(5 * time.Second)
}Level 4: 네트워크와 실전 응용 (86-100)
86-95: 네트워크 프로그래밍
문제 86: TCP 서버 만들기
// 답안
import "net"
func handleConnection(conn net.Conn) {
defer conn.Close()
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("Connection closed")
return
}
message := string(buffer[:n])
fmt.Printf("Received: %s", message)
// 에코
conn.Write(buffer[:n])
}
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
fmt.Println("Server listening on :8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
continue
}
go handleConnection(conn)
}
}문제 87: TCP 클라이언트 만들기
// 답안
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
panic(err)
}
defer conn.Close()
// 메시지 전송
message := "Hello, Server!"
_, err = conn.Write([]byte(message))
if err != nil {
panic(err)
}
// 응답 받기
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
panic(err)
}
fmt.Printf("Server response: %s\n", string(buffer[:n]))
}문제 88: HTTP 서버 만들기
// 답안
import "net/http"
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func jsonHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response := map[string]interface{}{
"message": "Hello, JSON!",
"status": "success",
"data": []int{1, 2, 3, 4, 5},
}
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/hello/", helloHandler)
http.HandleFunc("/json", jsonHandler)
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}문제 89: HTTP 클라이언트로 API 호출하기
// 답안
func main() {
// GET 요청
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Printf("Response: %s\n", string(body))
// POST 요청
postData := map[string]interface{}{
"title": "My Post",
"body": "This is the body",
"userId": 1,
}
jsonData, _ := json.Marshal(postData)
postResp, err := http.Post(
"https://jsonplaceholder.typicode.com/posts",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer postResp.Body.Close()
postBody, _ := ioutil.ReadAll(postResp.Body)
fmt.Printf("POST Response: %s\n", string(postBody))
}문제 90: RPC 서버 만들기
// 답안
import (
"net"
"net/http"
"net/rpc"
)
type Calculator struct{}
type Args struct {
A, B int
}
func (c *Calculator) Add(args *Args, result *int) error {
*result = args.A + args.B
return nil
}
func (c *Calculator) Multiply(args *Args, result *int) error {
*result = args.A * args.B
return nil
}
func main() {
calculator := &Calculator{}
rpc.Register(calculator)
rpc.HandleHTTP()
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
fmt.Println("RPC server starting on :8080")
http.Serve(listener, nil)
}문제 91: RPC 클라이언트 만들기
// 답안
func main() {
client, err := rpc.DialHTTP("tcp", "localhost:8080")
if err != nil {
panic(err)
}
defer client.Close()
// Add 호출
args := &Args{A: 5, B: 3}
var addResult int
err = client.Call("Calculator.Add", args, &addResult)
if err != nil {
panic(err)
}
fmt.Printf("5 + 3 = %d\n", addResult)
// Multiply 호출
var mulResult int
err = client.Call("Calculator.Multiply", args, &mulResult)
if err != nil {
panic(err)
}
fmt.Printf("5 * 3 = %d\n", mulResult)
}문제 92: WebSocket 서버 (gorilla/websocket 사용)
// 답안
// go get github.com/gorilla/websocket
import "github.com/gorilla/websocket"
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 모든 오리진 허용 (프로덕션에서는 제한 필요)
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Upgrade error:", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("Read error:", err)
break
}
fmt.Printf("Received: %s\n", message)
// 에코
err = conn.WriteMessage(messageType, message)
if err != nil {
fmt.Println("Write error:", err)
break
}
}
}
func main() {
http.HandleFunc("/ws", wsHandler)
fmt.Println("WebSocket server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}문제 93: 파일 업로드 HTTP 서버
// 답안
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
err := r.ParseMultipartForm(10 << 20) // 10 MB 제한
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, handler, err := r.FormFile("uploadfile")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
fmt.Printf("Uploaded File: %s\n", handler.Filename)
fmt.Printf("File Size: %d\n", handler.Size)
// 파일 저장
f, err := os.OpenFile("./uploads/"+handler.Filename,
os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer f.Close()
io.Copy(f, file)
fmt.Fprintf(w, "File uploaded successfully: %s", handler.Filename)
}
func main() {
os.MkdirAll("./uploads", os.ModePerm)
http.HandleFunc("/upload", uploadHandler)
fmt.Println("File upload server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}문제 94: 미들웨어 패턴으로 HTTP 서버
// 답안
type Middleware func(http.HandlerFunc) http.HandlerFunc
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r)
fmt.Printf("%s %s %v\n", r.Method, r.URL.Path, time.Since(start))
}
}
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer secret-token" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
func chainMiddleware(h http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, authenticated user!")
}
func publicHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is a public endpoint")
}
func main() {
protectedHandler := chainMiddleware(helloHandler,
loggingMiddleware, authMiddleware)
publicHandlerWithLogging := chainMiddleware(publicHandler,
loggingMiddleware)
http.HandleFunc("/protected", protectedHandler)
http.HandleFunc("/public", publicHandlerWithLogging)
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}문제 95: 로드 밸런서 구현하기
// 답안
import "sync/atomic"
type Backend struct {
URL string
Alive bool
}
type LoadBalancer struct {
backends []*Backend
current uint64
}
func (lb *LoadBalancer) GetNextBackend() *Backend {
if len(lb.backends) == 0 {
return nil
}
// 라운드 로빈
next := atomic.AddUint64(&lb.current, 1)
return lb.backends[next%uint64(len(lb.backends))]
}
func (lb *LoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
backend := lb.GetNextBackend()
if backend == nil {
http.Error(w, "No backend available", http.StatusServiceUnavailable)
return
}
// 프록시 요청
backendURL := backend.URL + r.URL.Path
resp, err := http.Get(backendURL)
if err != nil {
http.Error(w, "Backend error", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 응답 복사
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func main() {
backends := []*Backend{
{URL: "http://localhost:8081", Alive: true},
{URL: "http://localhost:8082", Alive: true},
{URL: "http://localhost:8083", Alive: true},
}
lb := &LoadBalancer{backends: backends}
fmt.Println("Load balancer starting on :8080")
log.Fatal(http.ListenAndServe(":8080", lb))
}96-100: 종합 실전 문제
문제 96: 채팅 서버 (WebSocket + 채널)
// 답안
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
fmt.Println("Client connected")
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
fmt.Println("Client disconnected")
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
break
}
c.hub.broadcast <- message
}
}
func (c *Client) writePump() {
defer c.conn.Close()
for {
select {
case message, ok := <-c.send:
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
c.conn.WriteMessage(websocket.TextMessage, message)
}
}
}
func serveWS(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
client := &Client{
hub: hub,
conn: conn,
send: make(chan []byte, 256),
}
client.hub.register <- client
go client.readPump()
go client.writePump()
}
func main() {
hub := &Hub{
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
go hub.Run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWS(hub, w, r)
})
fmt.Println("Chat server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}문제 97: 분산 워드카운트 (MapReduce 스타일)
// 답안
import (
"strings"
"unicode"
)
type KeyValue struct {
Key string
Value string
}
func Map(filename, contents string) []KeyValue {
ff := func(r rune) bool { return !unicode.IsLetter(r) }
words := strings.FieldsFunc(contents, ff)
var kva []KeyValue
for _, w := range words {
kv := KeyValue{strings.ToLower(w), "1"}
kva = append(kva, kv)
}
return kva
}
func Reduce(key string, values []string) string {
return fmt.Sprintf("%d", len(values))
}
func distributedWordCount(texts []string) map[string]int {
// Map 단계
mapResults := make(chan []KeyValue, len(texts))
var wg sync.WaitGroup
for i, text := range texts {
wg.Add(1)
go func(id int, content string) {
defer wg.Done()
result := Map(fmt.Sprintf("text-%d", id), content)
mapResults <- result
}(i, text)
}
go func() {
wg.Wait()
close(mapResults)
}()
// 중간 결과 수집 및 그룹화
intermediate := make(map[string][]string)
for kvs := range mapResults {
for _, kv := range kvs {
intermediate[kv.Key] = append(intermediate[kv.Key], kv.Value)
}
}
// Reduce 단계
result := make(map[string]int)
reduceResults := make(chan struct {
key string
count int
}, len(intermediate))
wg = sync.WaitGroup{}
for key, values := range intermediate {
wg.Add(1)
go func(k string, vals []string) {
defer wg.Done()
countStr := Reduce(k, vals)
count, _ := strconv.Atoi(countStr)
reduceResults <- struct {
key string
count int
}{k, count}
}(key, values)
}
go func() {
wg.Wait()
close(reduceResults)
}()
for r := range reduceResults {
result[r.key] = r.count
}
return result
}
func main() {
texts := []string{
"hello world hello",
"world of go programming",
"go is awesome go go",
}
result := distributedWordCount(texts)
fmt.Println("Word count results:")
for word, count := range result {
fmt.Printf("%s: %d\n", word, count)
}
}문제 98: 캐시 서버 (LRU 캐시)
// 답안
type Node struct {
key, value string
prev, next *Node
}
type LRUCache struct {
capacity int
cache map[string]*Node
head *Node
tail *Node
mutex sync.RWMutex
}
func NewLRUCache(capacity int) *LRUCache {
head := &Node{}
tail := &Node{}
head.next = tail
tail.prev = head
return &LRUCache{
capacity: capacity,
cache: make(map[string]*Node),
head: head,
tail: tail,
}
}
func (c *LRUCache) addNode(node *Node) {
node.prev = c.head
node.next = c.head.next
c.head.next.prev = node
c.head.next = node
}
func (c *LRUCache) removeNode(node *Node) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (c *LRUCache) moveToHead(node *Node) {
c.removeNode(node)
c.addNode(node)
}
func (c *LRUCache) popTail() *Node {
lastNode := c.tail.prev
c.removeNode(lastNode)
return lastNode
}
func (c *LRUCache) Get(key string) (string, bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
if node, exists := c.cache[key]; exists {
c.moveToHead(node)
return node.value, true
}
return "", false
}
func (c *LRUCache) Put(key, value string) {
c.mutex.Lock()
defer c.mutex.Unlock()
if node, exists := c.cache[key]; exists {
node.value = value
c.moveToHead(node)
} else {
newNode := &Node{key: key, value: value}
if len(c.cache) >= c.capacity {
tail := c.popTail()
delete(c.cache, tail.key)
}
c.cache[key] = newNode
c.addNode(newNode)
}
}
func (c *LRUCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
switch r.Method {
case "GET":
if value, exists := c.Get(key); exists {
w.WriteHeader(http.StatusOK)
w.Write([]byte(value))
} else {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Key not found"))
}
case "POST":
value := r.URL.Query().Get("value")
c.Put(key, value)
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func main() {
cache := NewLRUCache(100)
fmt.Println("Cache server starting on :8080")
fmt.Println("GET /?key=mykey")
fmt.Println("POST /?key=mykey&value=myvalue")
log.Fatal(http.ListenAndServe(":8080", cache))
}문제 99: 리더 선출 알고리즘 (Raft 스타일)
// 답안
import "math/rand"
type NodeState int
const (
Follower NodeState = iota
Candidate
Leader
)
type Node struct {
id int
state NodeState
currentTerm int
votedFor int
votes int
peers []*Node
electionTimer *time.Timer
heartbeatTimer *time.Timer
mutex sync.RWMutex
}
func NewNode(id int, peers []*Node) *Node {
n := &Node{
id: id,
state: Follower,
votedFor: -1,
peers: peers,
}
n.resetElectionTimer()
return n
}
func (n *Node) resetElectionTimer() {
timeout := time.Duration(150+rand.Intn(150)) * time.Millisecond
if n.electionTimer != nil {
n.electionTimer.Stop()
}
n.electionTimer = time.AfterFunc(timeout, n.startElection)
}
func (n *Node) startElection() {
n.mutex.Lock()
n.state = Candidate
n.currentTerm++
n.votedFor = n.id
n.votes = 1
term := n.currentTerm
n.mutex.Unlock()
fmt.Printf("Node %d starting election for term %d\n", n.id, term)
votes := make(chan bool, len(n.peers))
for _, peer := range n.peers {
if peer.id != n.id {
go func(p *Node) {
vote := n.requestVote(p, term)
votes <- vote
}(peer)
}
}
voteCount := 1
for i := 0; i < len(n.peers)-1; i++ {
if <-votes {
voteCount++
}
if voteCount > len(n.peers)/2 {
n.becomeLeader()
return
}
}
n.mutex.Lock()
n.state = Follower
n.mutex.Unlock()
n.resetElectionTimer()
}
func (n *Node) requestVote(peer *Node, term int) bool {
peer.mutex.Lock()
defer peer.mutex.Unlock()
if term < peer.currentTerm {
return false
}
if term > peer.currentTerm {
peer.currentTerm = term
peer.votedFor = -1
peer.state = Follower
}
if peer.votedFor == -1 || peer.votedFor == n.id {
peer.votedFor = n.id
peer.resetElectionTimer()
return true
}
return false
}
func (n *Node) becomeLeader() {
n.mutex.Lock()
n.state = Leader
n.mutex.Unlock()
fmt.Printf("Node %d became leader for term %d\n", n.id, n.currentTerm)
n.sendHeartbeats()
n.heartbeatTimer = time.AfterFunc(50*time.Millisecond, n.sendHeartbeats)
}
func (n *Node) sendHeartbeats() {
if n.state != Leader {
return
}
for _, peer := range n.peers {
if peer.id != n.id {
go func(p *Node) {
p.mutex.Lock()
if n.currentTerm >= p.currentTerm {
p.currentTerm = n.currentTerm
p.state = Follower
p.resetElectionTimer()
}
p.mutex.Unlock()
}(peer)
}
}
if n.state == Leader {
n.heartbeatTimer = time.AfterFunc(50*time.Millisecond, n.sendHeartbeats)
}
}
func main() {
const numNodes = 5
nodes := make([]*Node, numNodes)
// 모든 노드 생성
for i := 0; i < numNodes; i++ {
nodes[i] = &Node{
id: i,
state: Follower,
votedFor: -1,
peers: nodes,
}
}
// 각 노드 시작
for _, node := range nodes {
node.resetElectionTimer()
}
fmt.Println("Starting Raft leader election simulation...")
time.Sleep(10 * time.Second)
}문제 100: 간단한 분산 키-값 저장소
// 답안
type KVStore struct {
data map[string]string
mutex sync.RWMutex
peers []string
}
func NewKVStore(peers []string) *KVStore {
return &KVStore{
data: make(map[string]string),
peers: peers,
}
}
func (kv *KVStore) Get(key string) (string, bool) {
kv.mutex.RLock()
defer kv.mutex.RUnlock()
value, exists := kv.data[key]
return value, exists
}
func (kv *KVStore) Put(key, value string) error {
kv.mutex.Lock()
kv.data[key] = value
kv.mutex.Unlock()
// 다른 노드들에게 복제
return kv.replicate(key, value)
}
func (kv *KVStore) replicate(key, value string) error {
var wg sync.WaitGroup
errors := make(chan error, len(kv.peers))
for _, peer := range kv.peers {
wg.Add(1)
go func(peerAddr string) {
defer wg.Done()
data := map[string]string{"key": key, "value": value}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://"+peerAddr+"/replicate",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
errors <- err
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
errors <- fmt.Errorf("replication failed for %s", peerAddr)
}
}(peer)
}
go func() {
wg.Wait()
close(errors)
}()
// 최소 하나의 복제 성공 확인
errorCount := 0
for err := range errors {
if err != nil {
fmt.Printf("Replication error: %v\n", err)
errorCount++
}
}
if errorCount == len(kv.peers) {
return fmt.Errorf("all replications failed")
}
return nil
}
func (kv *KVStore) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
key := r.URL.Query().Get("key")
if value, exists := kv.Get(key); exists {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"value": value})
} else {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"error": "Key not found"})
}
case "POST":
var data map[string]string
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
key := data["key"]
value := data["value"]
if r.URL.Path == "/replicate" {
// 복제 요청 처리 (로컬만)
kv.mutex.Lock()
kv.data[key] = value
kv.mutex.Unlock()
w.WriteHeader(http.StatusOK)
} else {
// 일반 PUT 요청 (복제 포함)
if err := kv.Put(key, value); err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
} else {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func main() {
port := flag.String("port", "8080", "Server port")
peers := flag.String("peers", "", "Comma-separated list of peer addresses")
flag.Parse()
var peerList []string
if *peers != "" {
peerList = strings.Split(*peers, ",")
}
store := NewKVStore(peerList)
fmt.Printf("KV Store starting on port %s\n", *port)
fmt.Printf("Peers: %v\n", peerList)
log.Fatal(http.ListenAndServe(":"+*port, store))
}🎯 학습 완료 체크리스트
Level 1 완료 (1-30) ✅
- 변수 선언과 타입 시스템
- 조건문과 반복문
- 배열과 슬라이스 조작
- 맵 사용법과 순회
Level 2 완료 (31-60) ✅
- 함수 정의와 고급 기능
- 구조체와 메서드
- 인터페이스와 다형성
- JSON 처리
Level 3 완료 (61-85) ✅
- 고루틴 기본 사용법
- 채널을 통한 통신
- 동시성 제어 (Mutex, WaitGroup)
- 고급 동시성 패턴
Level 4 완료 (86-100) ✅
- TCP/HTTP 네트워크 프로그래밍
- RPC 구현
- WebSocket 실시간 통신
- 분산 시스템 패턴
🚀 다음 단계
이 100문항을 완료했다면:
- MIT 6.824 Lab 준비 완료!
- Go 언어 중급 수준 달성
- 분산 시스템 구현 기초 완성
이제 자신있게 MIT_6824_주말_학습_가이드의 Phase 1으로 넘어가세요! 🎉