Skip to content

Date: April 01, 2024

Goroutine and Concurrent Programming

pods
Golang concurrency

Table of Content

Goroutine

  • ์Šค๋ ˆ๋“œ๋ž€?
  • ๊ณ ๋ฃจํ‹ด: ๊ฒฝ๋Ÿ‰ ์Šค๋ ˆ๋“œ๋กœ ํ•จ์ˆ˜๋‚˜ ๋ช…๋ น์„ ๋™์‹œ์— ์‹คํ–‰ ์‹œ ์‚ฌ์šฉ. main()๋„ ๊ณ ๋ฃจํ‹ด์— ์˜ํ•ด ์‹คํ–‰ ๋จ
  • ํ”„๋กœ์„ธ์Šค 1๊ฐœ(์‹ฑ๊ธ€ํ…Œ์Šคํ‚น) vs ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค (๋ฉ€ํ‹ฐํƒœ์Šคํ‚น).
  • ํ”„๋กœ์„ธ์Šค : ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์— ๋กœ๋”ฉ๋˜์–ด ๋™์ž‘ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ. 1๊ฐœ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
    • ์Šค๋ ˆ๋“œ๋Š” ํ”„๋กœ์„ธ์Šค์˜ ์„ธ๋ถ€์ž‘์—… ๋‹จ์œ„ ๋˜๋Š” ์‹คํ–‰ ํ๋ฆ„. 1๊ฐœ, 2๊ฐœ, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ์žˆ์„ ์ˆ˜ ์žˆ์Œ
  • CPU๋Š” ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ ์‹คํ–‰๊ฐ€๋Šฅ, ์—ฌ๋Ÿฌ๊ฐœ ์Šค๋ ˆ๋“œ๋ฅผ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉด์„œ ์ˆ˜ํ–‰ํ•˜์—ฌ ๋™์‹œ์‹คํ–‰ ์ฒ˜๋Ÿผ ๋ณด์ž„

  • ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ

  • ํ•˜๋‚˜์˜ CPU๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ thread ์ „ํ™˜ํ•˜๋ฉด์„œ ์ˆ˜ํ–‰์‹œ ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ ๋ฐœ์ƒ
  • ์Šค๋ ˆ๋“œ ์ „ํ™˜์‹œ ํ˜„์žฌ ์ƒํƒœ ๋ณด๊ด€ํ•ด์•ผ ํ•จ -> ์Šค๋ ˆ๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ €์žฅ
  • ์Šค๋ ˆ๋“œ ์ปจํ…์ŠคํŠธ : ๋ช…๋ น ํฌ์ธํ„ฐ, ์Šคํƒ๋ฉ”๋ชจ๋ฆฌ ๋“ฑ
  • ์ปจํ…์ŠคํŠธ ์ €์žฅ ๋ฐ ๋ณต์› ์‹œ ๋น„์šฉ ๋ฐœ์ƒ
  • CPU ์ฝ”์–ด๋งˆ๋‹ค OS์Šค๋ ˆ๋“œ๋ฅผ ํ•˜๋‚˜๋งŒ ํ• ๋‹นํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, Go์–ธ์–ด๋Š” ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ ๋ฐœ์ƒ ์—†์Œ!

  • ๊ณ ๋ฃจํ‹ด ์‚ฌ์šฉ

  • ๋ชจ๋“  ํ”„๋กœ๊ทธ๋žจ์€ main()์„ ๊ณ ๋ฃจํ‹ด์œผ๋กœ ํ•˜๋‚˜ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
  • go ํ•จ์ˆ˜ํ˜ธ์ถœ๋กœ ์ƒˆ๋กœ์šด ๊ณ ๋ฃจํ‹ด ์ถ”๊ฐ€ ๊ฐ€๋Šฅ.
  • ํ˜„์žฌ ๊ณ ๋ฃจํ‹ด์ด ์•„๋‹Œ ์ƒˆ๋กœ์šด ๊ณ ๋ฃจํ‹ด์—์„œ ํ•จ์ˆ˜๊ฐ€ ์ˆ˜ํ–‰ ๋จ

  • Goroutines

  • Goroutines are lightweight concurrent functions or threads in Go.
  • They allow you to perform multiple tasks concurrently, leveraging parallelism on multi-core systems.
  • Unlike traditional threads, Goroutines are managed by the Go runtime, which dynamically allocates and manages their stack size.
  • Goroutines communicate using channels, which prevent race conditions when accessing shared memory.
  • Example: Fetching data from multiple APIs concurrently using Goroutines:
package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    urls := []string{
        "https://api.example.com/users",
        "https://api.example.com/products",
        "https://api.example.com/orders",
    }

    results := make(chan string)

    for _, url := range urls {
        go fetchAPI(ctx, url, results)
    }

    for range urls {
        fmt.Println(<-results)
    }
}

func fetchAPI(ctx context.Context, url string, results chan<- string) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        results <- fmt.Sprintf("Error creating request for %s: %s", url, err.Error())
        return
    }

    client := http.DefaultClient
    resp, err := client.Do(req)
    if err != nil {
        results <- fmt.Sprintf("Error making request to %s: %s", url, err.Error())
        return
    }
    defer resp.Body.Close()

    results <- fmt.Sprintf("Response from %s: %d", url, resp.StatusCode)
}

โ†‘ Back to top

  • main ๊ณ ๋ฃจํ‹ด์™ธ์— PrintHangul, PrintNumber ๊ณ ๋ฃจํ‹ด 2๊ฐœ ์ถ”๊ฐ€์ƒ์„ฑ
  • 3๊ฐœ ๊ณ ๋ฃจํ‹ด์ด ๋™์‹œ์— ์‹คํ–‰๋จ(CPU 3์ฝ”์–ด์ด์ƒ)
  • 1-์ฝ”์–ด CPU์—์„œ๋Š” ๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ ๋ณด์ž„
  • main ๊ณ ๋ฃจํ‹ด์€ ์ข…๋ฃŒํ•˜๋ฉด ๋ชจ๋“  ๊ณ ๋ฃจํ‹ด์ด ์ฆ‰์‹œ ์ข…๋ฃŒ๋˜๊ณ , ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋จ
    • main ๊ณ ๋ฃจํ‹ด์—์„œ 3์ดˆ๊ฐ„ ๊ธฐ๋‹ค๋ ค์„œ ๋‚˜๋จธ์ง€ 2๊ฐœ ๊ณ ๋ฃจํ‹ด๋„ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ waitํ•จ
package main

import (
  "fmt"
  "time"
)

func PrintHangul() {
  hanguls := []rune{'๊ฐ€','๋‚˜','๋‹ค','๋ผ','๋งˆ','๋ฐ”', '์‚ฌ'}
  for _, v := range hanguls {
    time.Sleep(300 * time.Millisecond)
    fmt.Printf("%c", v)
  }
}
func PrintNumbers(){
  for i:=1; i<=5; i++ {
    time.Sleep(400 * time.Millisecond)
    fmt.Printf("%d ", i)
  }
}

func main() {
  // PrintHangul, PrintNumbers๊ฐ€ ๋™์‹œ์— ์‹คํ–‰
  go PrintHangul()
  go PrintNumbers()

  // ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์œผ๋ฉด ๋ฉ”์ธํ•จ์ˆ˜ ์ข…๋ฃŒ๋˜๊ณ  ๋ชจ๋“  ๊ณ ๋ฃจํ‹ด๋„ ์ข…๋ฃŒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๊ธฐ์‹œ๊ฐ„ ์ง€์ •
  // ํ•˜์ง€๋งŒ 3์ดˆ ๋ผ๋Š” ์‹œ๊ฐ„์ฒ˜๋Ÿผ ํ•ญ์ƒ ์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•  ํ•„์š”๋Š” ์—†์Œ
  //  -> syncํŒจํ‚ค์ง€ WaitGroup ๊ฐ์ฒด ์‚ฌ์šฉ!
  time.Sleep(3*time.Second)
}

โ†‘ Back to top

sync.WaitGroup

  • synchronization primitives in go

  • ์„œ๋ธŒ ๊ณ ๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ

  • ํ•ญ์ƒ ๊ณ ๋ฃจํ‹ด์˜ ์ข…๋ฃŒ์‹œ๊ฐ„์— ๋งž์ถฐ time.Sleep(์ข…๋ฃŒ๊นŒ์ง€๊ฑธ๋ฆฌ๋Š”์‹œ๊ฐ„) ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Œ
  • ๊ณ ๋ฃจํ‹ด์ด ๋๋‚ ๋•Œ๊นŒ์ง€ waitํ• ์ˆ˜ ์žˆ์Œ: sync.WaitGroup ๊ฐ์ฒด

  • A WaitGroup is a synchronization primitive in Go that allows you

  • to wait for a collection of goroutines to finish executing.
  • It keeps track of the number of goroutines that are currently active.
    • Add(n int) increases the internal counter by n, indicating that n goroutines will be added to the group.
    • Done() decreases the counter by 1 when a goroutine completes its work.
    • Wait() blocks until the counter becomes zero (i.e., all goroutines have finished).
// sync.WaitGroup ๊ฐ์ฒด ์‚ฌ์šฉ
var wg sync.WaitGroup

// Add()๋กœ ์™„๋ฃŒํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…๊ฐœ์ˆ˜ ์„ค์ •ํ•˜๊ณ , ๊ฐ ์ž‘์—…์ด ์™„๋ฃŒ ๋ ๋•Œ๋งˆ๋‹ค Done() ํ˜ธ์ถœํ•˜์—ฌ
// ๋‚จ์€ ์ž‘์—…๊ฐœ์ˆ˜๋ฅผ ํ•˜๋‚˜์”ฉ ์ค„์—ฌ์คŒ. Wait()์€ ์ „์ฒด ์ž‘์—…์ด ๋ชจ๋‘ ์™„๋ฃŒ๋ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋จ
wg.Add(3)   // ์ž‘์—…๊ฐœ์ˆ˜ ์„ค์ •
wg.Done()   // ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
wg.Wait()   // ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
package main

import (
  "sync"
  "fmt"
)

var wg sync.WaitGroup

func SumAtoB(a, b int) {
  sum := 0
  for i:=a; i<=b; i++ {
    sum += i
  }
  fmt.Printf("%d๋ถ€ํ„ฐ %d๊นŒ์ง€ ํ•ฉ๊ณ„๋Š” %d์ž…๋‹ˆ๋‹ค.\n", a,b,sum)

  wg.Done() // wg์˜ ๋‚จ์€ ์ž‘์—…๊ฐœ์ˆ˜๋ฅผ 1์”ฉ ๊ฐ์†Œ์‹œํ‚ด
}

func main() {
  // Set the total work count: 10 goroutines will be created, increasing CPU utilization
  wg.Add(10) // ์ด ์ž‘์—…๊ฐœ์ˆ˜ ์„ค์ •: 10๊ฐœ์˜ ๊ณ ๋ฃจํ‹ด ์ƒ์„ฑํ•˜์—ฌ CPU ์ ์œ ์œจ ์ฆ๊ฐ€

  for i:=0; i<10; i++ {
    go SumAtoB(1, 1000000)
  }

  wg.Wait() // ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ ๋ ๋•Œ๊นŒ์ง€ (๋‚จ์€์ž‘์—…๊ฐœ์ˆ˜ = 0) ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ  ๋Œ€๊ธฐ
  fmt.Println("๋ชจ๋“  ๊ณ„์‚ฐ์ด ์™„๋ฃŒ๋์Šต๋‹ˆ๋‹ค.")
}

โ†‘ Back to top

  • synchronization primitives in go
  • https://medium.com/better-programming/using-synchronization-primitives-in-go-mutex-waitgroup-once-2e50359cb0a7

  • wait-group.example.go

package main

import (
    "fmt"
    "os"
    "sync"
)

func main() {
    homeDir, err := os.UserHomeDir()
    if err != nil {
        panic(err)
    }
    filesInHomeDir, err := os.ReadDir(homeDir)
    if err != nil {
        panic(err)
    }

    var wg sync.WaitGroup
    wg.Add(len(filesInHomeDir))

    fmt.Println("Printing files in", homeDir)

    for _, file := range filesInHomeDir {
    // wg.Add(1) // this also works instead of `wg.Add(len(filesInHomeDir))`
        // anon function with parameter type `os.DisEntry`
        // The `(file)` at the end of the expression immediately invokes
        // the anonymous function with the value of the file variable.
        go func(f os.DirEntry) {
            defer wg.Done()
            fmt.Println(f.Name())
        }(file)
    }

    wg.Wait()
    fmt.Println("finished....")
}

โ†‘ Back to top

sync.Once

  • once-example.go
  • Say you're building a REST API using the Go net/http package and you want a piece of code to be executed
  • only when the HTTP handler is called (e.g. a get a DB connection).
  • You can wrap that code with once.Do and rest assured that
  • it will be only run when the handler is invoked for the first time.
package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

var once sync.Once

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("http handler start")
        once.Do(oneTimeOp)
        fmt.Println("http handler end")
        w.Write([]byte("done!"))
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

func oneTimeOp() {
    fmt.Println("one time op start")
    time.Sleep(3 * time.Second)
    fmt.Println("one time op end")
}

โ†‘ Back to top

How Goroutine works

  • ๊ณ ๋ฃจํ‹ด์€ ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋‹จ์ผ ํ๋ฆ„์œผ๋กœ, OS ์Šค๋ ˆ๋“œ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ๋Ÿ‰ ์Šค๋ ˆ๋“œ
  • 2-Core ์ปดํ“จํ„ฐ ๊ฐ€์ •
  • Goroutine 1๊ฐœ ์ผ๋•Œ:
  • Core1-OsThread1-GoRoutine1
  • Goroutine 2๊ฐœ ์ผ๋•Œ:
  • Core1-OsThread1-GoRoutine1
  • Core2-OsThread2-GoRoutine2
  • Goroutine 3๊ฐœ ์ผ๋•Œ
  • Core1-OsThread1-GoRoutine1
  • Core2-OsThread2-GoRoutine2
  • ์ž‘์—…๋๋‚ ๋•Œ ๊นŒ์ง€ ๋Œ€๊ธฐ ํ›„ ๋๋‚œ 2์ฝ”์–ด์— '๊ณ ๋ฃจํ‹ด_3' ๋ฐฐ์ •๋จ
  • Core1-OsThread1-GoRoutine1
  • Core2-OsThread2-GoRoutine3
  • GoRoutine3๊ฐ€ ์‹œ์Šคํ…œ์ฝœ ํ˜ธ์ถœ ํ›„ ๋„คํŠธ์›Œํฌ ๋Œ€๊ธฐ์ƒํƒœ ์ผ๋•Œ:
  • GoRoutine3๊ฐ€ ๋Œ€๊ธฐ๋ชฉ๋ก์œผ๋กœ ๋น ์ง€๊ณ , GoRoutin4๊ฐ€ ์Šค๋ ˆ๋“œ2๋ฅผ ์ด์šฉํ•˜์—ฌ ์‹คํ–‰๋จ
  • Core1-OsThread1-GoRoutine1
  • Core2-OsThread2-GoRoutine4
  • ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ์€ CPU๊ฐ€ Thread๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์ฝ”์–ด์™€ ์Šค๋ ˆ๋“œ๋Š” ๋ณ€๊ฒฝ๋˜์ง€์•Š๊ณ , ์˜ค๋กœ์ง€ ๊ณ ๋ฃจํ‹ด๋งŒ ์˜ฎ๊ฒจ ๋‹ค๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—,
  • ๊ณ ๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋ฉด, ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ ๋น„์šฉ์ด ๋ฐœ์ƒํ•˜์ง€์•Š๋Š”๋‹ค!

  • ์‹œ์Šคํ…œ ์ฝœ ํ˜ธ์ถœ ์‹œ (์šด์˜์ฒด์ œ๊ฐ€ ์ง€์›ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœ)

  • (๊ณ ๋ฃจํ‹ด์œผ๋กœ ์‹œ์Šคํ…œ์ฝœ ํ˜ธ์ถœ์‹œ; e.g. ๋„คํŠธ์›Œํฌ๋กœ ๋ฐ์ดํ„ฐ ์ฝ์„ ๋•Œ ๋ฐ์ดํ„ฐ ๋“ค์–ด์˜ฌ๋•Œ ๊นŒ์ง€ ๊ณ ๋ฃจํ‹ด์ด ๋Œ€๊ธฐ์ƒํƒœ ๋จ),
  • ๋„คํŠธ์›Œํฌ ์ˆ˜์‹  ๋Œ€๊ธฐ์ƒํƒœ์ธ ๊ณ ๋ฃจํ‹ด์ด ๋Œ€๊ธฐ๋ชฉ๋ก์œผ๋กœ ๋น ์ง€๊ณ , ๋Œ€๊ธฐ์ค‘์ด๋˜ ๋‹ค๋ฅธ ๊ณ ๋ฃจํ‹ด์ด OS ์Šค๋ ˆ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์‹คํ–‰ ๋จ
  • ์ฝ”์–ด์™€ ์Šค๋ ˆ๋“œ ๋ณ€๊ฒฝ(์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ) ์—†์ด ๊ณ ๋ฃจํ‹ด์ด ์˜ฎ๊ฒจ๋‹ค๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ํšจ์œจ์ 
  • ์ฝ”์–ด๊ฐ€ ์Šค๋ ˆ๋“œ ์˜ฎ๊ฒจ๋‹ค๋‹ˆ๋Š” ์ปจํ…์ŠคํŠธ ์Šค์œ„์นญ์„ ํ•˜์ง€ ์•Š๊ณ , ๋Œ€์‹  ๊ณ ๋ฃจํ‹ด์ด ์ง์ ‘ ๋Œ€๊ธฐ์ƒํƒœ <-> ์‹คํ–‰์ƒํƒœ ์Šค์œ„์นญ ์˜ฎ๊ฒจ๋‹ค๋…€์„œ ํšจ์œจ์ 

โ†‘ Back to top

Concurrent programming precautions

  • ๋ฌธ์ œ์ : ํ•˜๋‚˜์˜/๋™์ผํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ž์›์— ์—ฌ๋Ÿฌ๊ฐœ ๊ณ ๋ฃจํ‹ด ์ ‘๊ทผ!
  • e.g. ์ž…๊ธˆ1000, ์ถœ๊ธˆ1000 ์„ 10๊ฐœ์˜ ๊ณ ๋ฃจํ‹ด์ด ๋™์‹œ ์‹คํ–‰ ํ•˜๋Š” ์ƒํ™ฉ
  • ๋‘๊ฐœ ๊ณ ๋ฃจํ‹ด์ด ๊ฐ๊ฐ 1000์› ์ž…๊ธˆํ–ˆ๋Š”๋ฐ 2000์ด ์•„๋‹Œ 1000์ด๋œ์ƒํƒœ์—์„œ ๋‹ค์‹œ ๋‘๋ฒˆ ์ถœ๊ธˆ์‹œ < 0 : panic!
  • race condition
  • ํ•ด๊ฒฐ์ฑ…: ํ•œ ๊ณ ๋ฃจํ‹ด์—์„œ ๊ฐ’์„ ๋ณ€๊ฒฝํ• ๋•Œ ๋‹ค๋ฅธ ๊ณ ๋ฃจํ‹ด์ด ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก mutex ํ™œ์šฉ
  • mutual exclusion

  • A race condition occurs when two or more threads access shared data and attempt to modify it simultaneously.

  • To prevent race conditions, you typically use locks or synchronization mechanisms.
  • A lock ensures that only one thread can access the shared data at a time.

  • Example of race condition

package main
import (
  "fmt"
  "sync"
  "time"
)

type Account struct {
  Balance int
}

func DepositAndWithdraw(account *Account) {
  if account.Balance < 0 {
    panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance))
  }
  account.Balance += 1000
  time.Sleep(time.Millisecond)
  account.Balance -= 1000
}

func main() {
  var wg sync.WaitGroup

  account := &Account{0}
  wg.Add(10)

  for i:=0; i< 10; i++ {
    // ํ•˜๋‚˜์˜ ์ž์›์— ๋‹ค์ˆ˜์˜ ๊ณ ๋ฃจํ‹ด์ด ์ ‘๊ทผ ํ•จ
    // ๋ฎคํ…์Šค๋ฅผ ํ†ตํ•ด Lock ๊ฑธ์–ด ํ•ด๊ฒฐ
    go func() {
      for {
        DepositAndWithdraw(account)
      }
      wg.Done()
    }()
  }

  wg.Wait()
}

โ†‘ Back to top

Solving concurrency problems using mutexes

  • ํ•œ ๊ณ ๋ฃจํ‹ด์—์„œ ๊ฐ’์„ ๋ณ€๊ฒฝํ• ๋•Œ ๋‹ค๋ฅธ ๊ณ ๋ฃจํ‹ด์ด ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก mutex ํ™œ์šฉ: mutual exclusion
  • mutex.Lock()์œผ๋กœ mutex ํš๋“ mutext.Unlock()์œผ๋กœ mutex ๋ฐ˜๋‚ฉ
  • ๋‹ค๋ฅธ ๊ณ ๋ฃจํ‹ด์ด ์ด๋ฏธ ๋ฎคํ…์Šค๋ฅผ ํš๋“ํ–ˆ๋‹ค๋ฉด ํ•ด๋‹น ๊ณ ๋ฃจํ‹ด์ด ๋ฎคํ…์Šค๋ฅผ ๋†“์„ ๋•Œ(mutex.Unlock()) ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
  • ํ•˜์ง€๋งŒ ์˜ค์ง ํ•˜๋‚˜์˜ ๊ณ ๋ฃจํ‹ด๋งŒ ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋žจ ์„ฑ๋Šฅ ํ–ฅ์ƒ ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง..
  • ๋˜ํ•œ Deadlock ๋ฐœ์ƒ ๊ฐ€๋Šฅ!

  • A mutex (short for โ€œmutual exclusionโ€) is a synchronization primitive used

  • to protect shared resources in concurrent programs. It ensures that
  • only one goroutine can access a critical section of code (a shared resource) at any given time.
  • Mutexes prevent race conditions by allowing exclusive access to data.
package main

import (
  "fmt"
  "sync"
  "time"
)

var mutex sync.Mutex

type Account struct {
  Balance int
}

func DepositAndWithdraw(account *Account) {
  // ๋ฎคํ…์Šค ํš๋“!
  mutex.Lock()

  // ํ•œ๋ฒˆ ํš๋“ํ•œ ๋ฎคํ…์Šค๋Š” ๋ฐ˜๋“œ์‹œ Unlock() ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ˜๋‚ฉ
  // `defer`: ํ•จ์ˆ˜ ์ข…๋ฃŒ ์ „์— ๋ฎคํ…์Šค Unlock() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
  defer mutex.Unlock()

  if account.Balance < 0 {
    panic(fmt.Sprint("Balance cannot be negative: %d", account.Balance))
  }

  account.Balance += 1000
  time.Sleep(time.Millisecond)
  account.Balance -= 1000
}


func main() {
  var wg sync.WaitGroup

  account := &Account{0}
  wg.Add(10)
  for i:=0; i< 10; i++ {
    go func() {
      for {
        DepositAndWithdraw(account)
      }
      wg.Done()
    }()
  }
  wg.Wait()

}

โ†‘ Back to top

mutex to ensure atomic access to a shared variable

A mutex helps achieve atomic access by allowing only one thread to hold the lock (mutex) at any given time. The following code achieve 1.concurrency and 2.preventing race conditions.

Using mutex and WaitGroup

package main

import (
    "fmt"
    "sync"
)

type counter struct {
    i  int64
    wg sync.WaitGroup
    mu sync.Mutex
}

func (c *counter) increment() {
    defer c.wg.Done()
    c.mu.Lock()
    c.i += 1
    c.mu.Unlock()
}

func main() {
    c := counter{i: 0}

    for i := 0; i < 1000; i++ {
        c.wg.Add(1)
        go c.increment()
    }

    c.wg.Wait()

    fmt.Println("Final Counter Value:", c.i)
}

โ†‘ Back to top

Using mutex and done channel

  • The done channel is used without a sync.WaitGroup.
  • Each worker goroutine sends a signal to the done channel when it completes its task.
  • The main goroutine waits for all workers to finish by receiving from the done channel.
  • This approach allows us to manage concurrency without using a sync.WaitGroup
package main

import (
    "fmt"
    "runtime"
    "sync"
)

const initialValue = -500

type counter struct {
    i int64
    mu sync.Mutex  // ๊ณต์œ  ๋ฐ์ดํ„ฐ i๋ฅผ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ๋ฎคํ…์Šค
    once sync.Once // ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•œ Once ๊ตฌ์กฐ์ฒด
}

// counter ๊ฐ’์„ 1์”ฉ ์ฆ๊ฐ€์‹œํ‚ด
func (c *counter) increment() {
    // i ๊ฐ’ ์ดˆ๊ธฐํ™” ์ž‘์—…์€ ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰๋˜๋„๋ก once์˜ Do() ๋ฉ”์„œ๋“œ๋กœ ์‹คํ–‰
    c.once.Do(func() {
        c.i = initialValue
    })

    c.mu.Lock()   // i ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ถ€๋ถ„(์ž„๊ณ„ ์˜์—ญ)์„ ๋ฎคํ…์Šค๋กœ ์ž ๊ธˆ
    c.i += 1      // ๊ณต์œ  ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ
    c.mu.Unlock() // i ๊ฐ’์„ ๋ณ€๊ฒฝ ์™„๋ฃŒํ•œ ํ›„ ๋ฎคํ…์Šค ์ž ๊ธˆ ํ•ด์ œ
}

// counter์˜ ๊ฐ’์„ ์ถœ๋ ฅ
func (c *counter) display() {
    fmt.Println(c.i)
}

func main() {
    // ๋ชจ๋“  CPU๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•จ
    runtime.GOMAXPROCS(runtime.NumCPU())

    c := counter{i: 0}          // ์นด์šดํ„ฐ ์ƒ์„ฑ
    done := make(chan struct{}) // ์™„๋ฃŒ ์‹ ํ˜ธ ์ˆ˜์‹ ์šฉ ์ฑ„๋„

    // c.increment()๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ณ ๋ฃจํ‹ด 1000๊ฐœ ์‹คํ–‰
    for i := 0; i < 1000; i++ {
        go func() {
            c.increment()      // ์นด์šดํ„ฐ ๊ฐ’์„ 1 ์ฆ๊ฐ€์‹œํ‚ด
            done <- struct{}{} // done ์ฑ„๋„์— ์™„๋ฃŒ ์‹ ํ˜ธ ์ „์†ก
        }()
    }

    // ๋ชจ๋“  ๊ณ ๋ฃจํ‹ด์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    for i := 0; i < 1000; i++ {
        <-done
    }

    c.display() // c์˜ ๊ฐ’ ์ถœ๋ ฅ
}

โ†‘ Back to top

More done channel example

https://gobyexample.com/closing-channels

package main

import "fmt"

func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    <-done

    _, ok := <-jobs
    fmt.Println("received more jobs:", ok)
}

โ†‘ Back to top

The problem with mutexes

  1. ๋ฎคํ…์Šค๋Š” ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์„ฑ๋Šฅ์ด์  ๊ฐ์†Œ์‹œํ‚ด
  2. ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
  3. e.g. ์‹ํƒ์— A์™€ B๊ฐ€ ๊ฐ๊ฐ ์ˆ˜์ €1, ํฌํฌ1 ์ง‘๊ณ ์žˆ์Œ.
  4. A,B๊ฐ€ ํฌํฌ1, ์ˆ˜์ €1 ์ง‘์œผ๋ ค ํ• ๋•Œ, A,B ๋ˆ„๊ตฌํ•˜๋‚˜ ์–‘๋ณดํ•˜์ง€ ์•Š์•„, ๋ฐฅ์„ ๋จน์„ ์ˆ˜ ์—†์Œ: ๋‘๊ฐœ mutex ๊ฐ๊ฐ ์ฐจ์ง€
  5. ์–ด๋–ค ๊ณ ๋ฃจํ‹ด๋„ ์›ํ•˜๋Š” ๋งŒํผ ๋ฎคํ…์Šค๋ฅผ ํ™•๋ณดํ•˜์ง€ ๋ชปํ•ด์„œ ๋ฌดํ•œํžˆ ๋Œ€๊ธฐํ•˜๋Š” ๊ฒฝ์šฐ; ๋ฐ๋“œ๋ฝ
  6. ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ™˜๊ฒฝ์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ณ ๋ฃจํ‹ด์œผ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๊ฐ€๋Šฅ
  7. ๊ฐ™์€๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ์‹œ ๊ผฌ์ผ ์ˆ˜ ์žˆ์Œ
  8. ๋ฎคํ…์Šค๋กœ ๊ณ ๋ฃจํ‹ด ํ•˜๋‚˜๋งŒ ์ ‘๊ทผํ•˜๋„๋ก ํ•˜์—ฌ ๊ผฌ์ด๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
  9. ํ•˜์ง€๋งŒ, ๋ฎคํ…์Šค๋ฅผ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅํ–ฅ์ƒ ์—†์ด ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ๊ฐ€๋Šฅ
  10. ๋ฎคํ…์Šค ์‚ฌ์šฉ์‹œ ์ข์€ ๋ฒ”์œ„์—์„œ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ ๋ฐฉ์ง€
  11. ๋˜๋Š” ๋‘˜๋‹ค ์ˆ˜์ €-> ํฌํฌ ์ˆœ์„œ๋กœ ๋ฎคํ…์Šค ๋ฝ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

  12. ๋ฉ€ํ‹ฐ์ฝ”์–ด ์ปดํ“จํ„ฐ์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ณ ๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ ํ–ฅ์ƒ

  13. ํ•˜์ง€๋งŒ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์—ฌ๋Ÿฌ ๊ณ ๋ฃจํ‹ด์ด ์ ‘๊ทผํ•˜๋ฉด ํ”„๋กœ๊ทธ๋žจ์ด ๊ผฌ์ผ ์ˆ˜ ์žˆ์Œ
  14. ๋ฎคํ…์Šค๋ฅผ ์ด์šฉํ•˜๋ฉด ๋™์‹œ์— ๊ณ ๋ฃจํ‹ด ํ•˜๋‚˜๋งŒ ์ ‘๊ทผํ•˜๋„๋ก ์ €์žฅํ•ด ๊ผฌ์ด๋Š” ๋ฌธ์ œ๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.
  15. ๊ทธ๋Ÿฌ๋‚˜ ๋ฎคํ…์Šค๋ฅผ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ํ–ฅ์ƒ๋„ ๋ชปํ•˜๊ณ  ๋ฐ๋“œ๋ฝ์ด๋ผ๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

  16. Deadlock in goroutines (not in using mutex): different scenario than above mutex deadlock

  17. A deadlock occurs when a group of goroutines are waiting for each other, and none of them can proceed.
  18. Essentially, they're stuck in a circular dependency, unable to make progress.

  19. Deadlock ์˜ˆ์‹œ

package main
import (
  "fmt"
  "math/rand"
  "sync"
  "time"
)

var wg sync.WaitGroup

func diningProblem(name string, first, second *sync.Mutex, firstName, secondName string) {

  for i:= 0; i<100; i++ {
    fmt.Printf("%s ๋ฐฅ์„ ๋จน์œผ๋ ค ํ•ฉ๋‹ˆ๋‹ค.\n", name)
    first.Lock()
    fmt.Printf("%s %s ํš๋“\n", name, fisrtName)
    second.Lock()
    fmt.Printf("%s %s ํš๋“\n", name, secondName)
    fmt.Printf("%s ๋ฐฅ์„ ๋จน์Šต๋‹ˆ๋‹ค.\n", name)

    time.Sleep(time.Duration(rand.Intn(1000))* time.Millisecond)

    second.Unlock()
    first.Unlock()
  }

  wg.Done()
}

func main() {
  rand.Seed(time.Now().UnixNano())

  wg.Add(2)
  fork := &sync.Mutex{}
  spoon := &sync.Mutex{}

  go diningProblem("A", fork, spoon, "ํฌํฌ", "์ˆ˜์ €")
  go diningProblem("B", spoon, fork, "์ˆ˜์ €", "ํฌํฌ")

  wg.Wait()
}

โ†‘ Back to top

Another Resource Management Technique

  • ๊ฐ ๊ณ ๋ฃจํ‹ด์ด ์„œ๋กœ ๋‹ค๋ฅธ ์ž์›์— ์ ‘๊ทผํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•
  • mutex์—†์ด ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅ

  • ์˜์—ญ์„ ๋‚˜๋ˆ„๋Š” ๋ฐฉ๋ฒ•

  • ๊ฐ ๊ณ ๋ฃจํ‹ด์€ ํ• ๋‹น๋œ ์ž‘์—…๋งŒ ํ•˜๋ฏ€๋กœ ๊ณ ๋ฃจํ‹ด(์ž‘์—…์ž)๊ฐ„ ๊ฐ„์„ญ ์—†์Œ
  • ๊ณ ๋ฃจํ‹ด ๊ฐ„ ๊ฐ„์„ญ์ด ์—†์–ด์„œ ๋ฎคํ…์Šค๋„ ํ•„์š” ์—†์Œ
  • ์—ญํ• ์„ ๋‚˜๋ˆ„๋Š” ๋ฐฉ๋ฒ• : Channel๊ณผ ํ•จ๊ป˜ ์„ค๋ช…

  • ๊ฐ ๊ณ ๋ฃจํ‹ด์€ ํ• ๋‹น๋œ ์ž‘์—…๋งŒ ํ•˜๋ฏ€๋กœ ๊ณ ๋ฃจํ‹ด๊ฐ„ ๊ฐ„์„ญ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„์„œ, Mutex๊ฐ€ ํ•„์š”์—†์Œ

package main
import (
  "fmt"
  "sync"
  "time"
)

type Job interface {
  Do()
}

type SquareJob struct {
  index int
}

func (j *SquareJob) Do() {
  fmt.Printf("%d ์ž‘์—… ์‹œ์ž‘\n", j.index)
  time.Sleep(1 * time.Second)
  fmt.Printf("%d ์ž‘์—… ์™„๋ฃŒ - ์ž‘์—…๊ฒฐ๊ณผ: %d\n", j.index, j.index * j.index)
}

func main() {
  jobList := [10]Job

  for i:=0 ; i< len(jobList); i++ {
    jobList[i] = &SquareJob{i}
  }

  var wg sync.WaitGroup
  wg.Add(10)

  for i:=0; i<10; i++ {
    job := jobList[i]
    go func() {
      job.Do()
      wg.Done()
    }
  }
  wg.Wait()
}

โ†‘ Back to top

Channel

  • ์ฑ„๋„: ๊ณ ๋ฃจํ‹ด๋ผ๋ฆฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์‹œ์ง€ ํ
  • ๋ฉ”์‹œ์ง€ํ์— ๋ฉ”์‹œ์ง€๊ฐ€ ์Œ“์ด๊ฒŒ ๋˜๊ณ 
  • ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์„ ๋•Œ๋Š” ์ฒ˜์Œ์˜จ ๋ฉ”์‹œ์ง€๋ถ€ํ„ฐ ์ฐจ๋ก€๋Œ€๋กœ ์ฝ์Œ

  • ์ฑ„๋„ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

  • ์ฑ„๋„์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € ์ฑ„๋„ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•จ

  • Channels are reference types, meaning they are already a reference to the underlying data structure.

  • square(ch chan int) {}
  • no need to declare channel parameters as pointers since channel is already a reference type
// ์ฑ„๋„ํƒ€์ž…: chan string
//    chan: ์ฑ„๋„ํ‚ค์›Œ๋“œ
//    string: ๋ฉ”์‹œ์ง€ ํƒ€์ž…
var messages chan string = make(chan string)
  1. ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋„ฃ๊ธฐ
var messages chan string = make(chan string)
messages <- "This is a message"
  1. ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ ๋นผ๊ธฐ
// ์ฑ„๋„์—์„œ ๋นผ๋‚ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ๋ณ€์ˆ˜
// "์ฑ„๋„ messages์— ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ฌ๋–„๊นŒ์ง€ '๋Œ€๊ธฐํ•จ'"
var msg string = <- messages
  1. ์ƒ์„ฑํ•œ goroutine์—์„œ ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋นผ๊ธฐ (consumer)
  2. main goroutine์—์„œ ๋ฐ์ดํ„ฐ ๋„ฃ๊ธฐ (provider)
package main
import (
  "fmt"
  "sync"
  "time"
)

func square(wg *sync.WaitGroup, ch chan int) {
  // ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ์˜จ๋‹ค
  // ๋ฐ์ดํ„ฐ ๋“ค์–ด์˜ฌ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
  n := <- ch

  time.Sleep(time.Second)
  fmt.Printf("Square: %d\n", n*n)

  wg.Done()
}

// main ๊ณ ๋ฃจํ‹ด๊ณผ square ๊ณ ๋ฃจํ‹ด์ด ๋™์‹œ ์‹คํ–‰
// main ๋ฃจํ‹ด์—์„œ ์ฑ„๋„์— 9๋ฅผ ๋„ฃ์–ด์ค„๋•Œ๊นŒ์ง€ square๋ฃจํ‹ด์€ ๋Œ€๊ธฐ์ƒํƒœ
// 1. main ๋ฃจํ‹ด
// 2. square() ๋ฃจํ‹ด
func main() {
  var wg sync.WaitGroup

  // ํฌ๊ธฐ 0์ธ ์ฑ„๋„ ์ƒ์„ฑ : ๋ฐ˜๋“œ์‹œ ๋‹ค๋ฅธ ๊ณ ๋ฃจํ‹ด์ด ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ ๊บผ๋‚ด์•ผ ์ •์ƒ ์ข…๋ฃŒ
  // ์–ด๋–ค ๊ณ ๋ฃจํ‹ด๋„ ๋ฐ์ดํ„ฐ ๋นผ์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๊ณ ๋ฃจํ‹ด์ด ๊ณ„์†๋Œ€๊ธฐ ํ•˜๋‹ค๊ฐ€ deadlock ๋ฐœ์ƒ
  ch := make(chan int)

  wg.Add(1)

  // ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ์„œ ์ฒ˜๋ฆฌ
  go square(&wg, ch)

  // ๋ฐ์ดํ„ฐ ๋„ฃ๋Š”๋‹ค.
  ch <- 9

  // square๋‚ด์—์„œ main๋ฃจํ‹ด์—์„œ ๋„ฃ์–ด์ค€ ์ฑ„๋„ ๋ฐ์ดํ„ฐ ๋นผ๊ณ  wg.Done() ์™„๋ฃŒ ๋ ๋–„๊นŒ์ง€ ๋Œ€๊ธฐ
  wg.Wait()
}
  1. ์ƒ์„ฑํ•œ goroutine์—์„œ ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋„ฃ๊ธฐ (provider)
  2. main goroutine์—์„œ ๋ฐ์ดํ„ฐ ๋บด๊ธฐ (consumer)
package main

import (
    "fmt"
    "sync"
)

func computeAndSendResult(wg *sync.WaitGroup, ch chan<- int) {
    defer wg.Done()
    // Perform some computation
    result := 42

    // Send the result through the channel
    ch <- result
}

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    resultCh := make(chan int)

  // produce
    go computeAndSendResult(&wg, resultCh)

  // consume
    // Receive the result from the channel
    result := <-resultCh
    fmt.Println("Received result:", result)

    wg.Wait()

}

โ†‘ Back to top

go channel with range and close

  • https://medium.com/better-programming/manging-go-channels-with-range-and-close-98f93f6e8c0c
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func produce(c chan<- int) {
    for i := 0; i < 5; i++ {
        c <- i
        fmt.Printf("producer put i=%d\n", i)
    }
    // Without closing channel, the consumer will wait indefinitely for channel
    close(c)
    fmt.Println("producer finished.\n")
}

func consume(c <-chan int) {
    fmt.Println("consumer sleeps for 5 seconds...")
    time.Sleep(5 * time.Second)
    fmt.Println("consumer started")
    for i := range c {
        fmt.Printf("consumer gets i = %d\n", i)
    }
    fmt.Println("consumer finished. press ctrl+c to exit")
}

func main() {
    // Both producer and consumer goroutine do not have to coexist
    // i.e. even if the producer goroutine finishes (and closes the channel),
    // the consumer goroutine range loop will receive all the values.
    c := make(chan int, 5)

    // producer
    go produce(c)

    // consumer
    go consume(c)

    e := make(chan os.Signal)
    // `signal.Notify` registers a channel `e` to receive specific signals
    // -> list of signals to capture i.e. `syscall.SIGINT`(Ctrl+c), `syscall.SIGTERM`(termination), etc...
    signal.Notify(e, syscall.SIGINT, syscall.SIGTERM)
    // blocks the main goroutine until one of these signals is received.
    <-e
}

โ†‘ Back to top

  • Modify above so that it works without using signal.Notify
    1. use done channel of type struct{}
    1. use sync.WaitGroup
  • use done channel of type struct{}

package main

import (
    "fmt"
    "time"
)

func produce(c chan<- int, done chan<- struct{}) {
    for i := 0; i < 5; i++ {
        c <- i
        fmt.Printf("producer put i=%d\n", i)
    }
    // Without closing channel, the consumer will wait indefinitely for channel
    close(c)
    fmt.Println("producer finished.\n")
    done <- struct{}{}
}

func consume(c <-chan int, done chan<- struct{}) {
    fmt.Println("consumer sleeps for 5 seconds...")
    time.Sleep(5 * time.Second)
    fmt.Println("consumer started")
    for i := range c {
        fmt.Printf("consumer gets i = %d\n", i)
    }
    // fmt.Println("consumer finished. press ctrl+c to exit")
    fmt.Println("consumer finished.")
    done <- struct{}{}
}

func main() {
    // Both producer and consumer goroutine do not have to coexist
    // i.e. even if the producer goroutine finishes (and closes the channel),
    // the consumer goroutine range loop will receive all the values.
    done := make(chan struct{})
    c := make(chan int, 5)

    // producer
    go produce(c, done)

    // consumer
    go consume(c, done)

    // in other cases use sync.WaitGroup to make main goroutine wait
    // e := make(chan os.Signal)
    // // `signal.Notify` registers a channel `e` to receive specific signals
    // // -> list of signals to capture i.e. `syscall.SIGINT`(Ctrl+c), `syscall.SIGTERM`(termination), etc...
    // signal.Notify(e, syscall.SIGINT, syscall.SIGTERM)
    // // blocks the main goroutine until one of these signals is received.
    // <-e
    <-done
    <-done
}
  1. use sync.WaitGroup
package main

import (
    "fmt"
    "sync"
    "time"
)

func produce(c chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 5; i++ {
        c <- i
        fmt.Printf("producer put i=%d\n", i)
    }
    // Without closing channel, the consumer will wait indefinitely for channel
    close(c)
    fmt.Println("producer finished.\n")
}

func consume(c <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Println("consumer sleeps for 5 seconds...")
    time.Sleep(5 * time.Second)
    fmt.Println("consumer started")
    for i := range c {
        fmt.Printf("consumer gets i = %d\n", i)
    }
    // fmt.Println("consumer finished. press ctrl+c to exit")
    fmt.Println("consumer finished.")
}

func main() {
    var wg sync.WaitGroup

    // Both producer and consumer goroutine do not have to coexist
    // i.e. even if the producer goroutine finishes (and closes the channel),
    // the consumer goroutine range loop will receive all the values.
    c := make(chan int, 5)

    wg.Add(2)

    // producer
    go produce(c, &wg)

    // consumer
    go consume(c, &wg)

    // in other cases use sync.WaitGroup to make main goroutine wait
    // e := make(chan os.Signal)
    // // `signal.Notify` registers a channel `e` to receive specific signals
    // // -> list of signals to capture i.e. `syscall.SIGINT`(Ctrl+c), `syscall.SIGTERM`(termination), etc...
    // signal.Notify(e, syscall.SIGINT, syscall.SIGTERM)
    // // blocks the main goroutine until one of these signals is received.
    // <-e
    wg.Wait()
}

โ†‘ Back to top

  • Both producer and consumer goroutine do not have to coexist
  • i.e. even if the producer goroutine finishes (and closes the channel),

    • the consumer goroutine range loop will receive all the values.
  • We can simulate this scenario by using a combination of:

  • A buffered channel in the producer
  • Delaying the consumer goroutine by adding a time.Sleep()

โ†‘ Back to top

Channel size

  • ๊ธฐ๋ณธ ์ฑ„๋„ํฌ๊ธฐ 0
  • ์ฑ„๋„ํฌ๊ธฐ0: ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋ณด๊ด€ํ•  ๊ณณ์ด ์—†์œผ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋นผ๊ฐˆ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
  • ์ฑ„๋„ ๋งŒ๋“ค๋•Œ ๋ฒ„ํผ ํฌ๊ธฐ ์„ค์ • ๊ฐ€๋Šฅ
package main

import (
  "fmt"
)

func main() {
  ch := make(chan int)
  // **DEADLOCK ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋„ฃ๊ธฐ๋งŒ ํ•˜๊ณ  ๋บด์ง€ ์•Š์„๋•Œ
  // ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋„ฃ์„๋–„ ๊ธฐ๋ณธ์‚ฌ์ด์ฆˆ 0์ด๊ธฐ ๋•Œ๋ฌธ์—
  // ๋ณด๊ด€ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ ๋นผ๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ์–ด์•ผ ์ง„ํ–‰๊ฐ€๋Šฅ!: goroutine 1๊ฐœ ์ƒ์„ฑํ•ด์„œ ํ•ด๋‹น func()์—์„œ <-ch ํ•ด์ค˜์•ผํ•จ
  // ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ! ํƒ๋ฐฐ ๋ณด๊ด€์žฅ์†Œ ์—†์œผ๋ฉด ๋ฌธ์•ž์—์„œ ๊ธฐ๋‹ค๋ ค์•ผ ํ•จ
  // ๋ฐ์ดํ„ฐ ๋ณด๊ด€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ์˜์—ญ: ๋ฒ„ํผ
  ch <- 9
  fmt.Println("Never print")
}

โ†‘ Back to top

  • ๋ฒ„ํผ ๊ฐ€์ง„ ์ฑ„๋„
  • var chan string messages = make(chan string, 2)
  • ๋ฒ„ํผ๊ฐ€ ๋‹ค ์ฐจ๋ฉด, ๋ฒ„ํผ๊ฐ€ ์—†๋Š” ํฌ๊ธฐ 0 ์ฑ„๋„์ฒ˜๋Ÿผ ๋นˆ์ž๋ฆฌ ์ƒ๊ธธ๋•Œ ๊นŒ์ง€ ๋Œ€๊ธฐ
  • ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ์ฃผ์ง€ ์•Š์œผ๋ฉด ๋ฒ„ํผ์—†์„ ๋•Œ ์ฒ˜๋Ÿผ ๊ณ ๋ฃจํ‹ด์ด ๋ฉˆ์ถ”๊ฒŒ๋จ

Waiting for Data in a Channel

  • ๊ณ ๋ฃจํ‹ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์† ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ์ž‘์—…์„ ์ˆ˜ํ–‰
package main

import (
  "fmt"
  "sync"
  "time"
)

func square(wg *sync.WaitGroup, ch chan int) {
  // ์ฑ„๋„์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด ์˜ฌ๋•Œ๊นŒ์ง€ '๊ณ„์†' ๊ธฐ๋‹ค๋ฆผ
  // ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€: square() ํ˜ธ์ถœ ๋ฐ–์—์„œ close(์ฑ„๋„)๋กœ ์ฑ„๋„์ด ๋‹ซํžˆ๋ฉด
  // for๋ฌธ์„ ์ข…๋ฃŒํ•˜์—ฌ ํ”„๋กœ๊ทธ๋žจ ์ •์ƒ ์ข…๋ฃŒํ•˜๋„๋ก ํ•จ
  for n := range ch {
    fmt.Printf("Square: %d\n", n*n)
    time.Sleep(time.Second)
  }

  // ์œ„์— for๋ฌธ์—์„œ ๊ณ„์† ์ฑ„๋„๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ์€, ์‹คํ–‰๋˜์ง€ ์•Š์Œ
  // ๋ฐ์ดํ„ฐ๋ฅผ ์ฑ„๋„ ch์— ๋ชจ๋‘ ๋„ฃ์€ ๋‹ค์Œ์— close(ch)๋กœ ์ฑ„๋„์„ ๋‹ซ์œผ๋ฉด
  // for eange์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ ๋‚˜์„œ ์ฑ„๋„์ด ๋‹ซํžŒ ์ƒํƒœ๋ผ๋ฉด for๋ฌธ ์ข…๋ฃŒํ•จ -> wg.Done() ์‹คํ–‰๋จ!
  wg.Done()
}

func main() {
  var wg sync.WaitGroup
  ch := make(chan int)

  wg.Add(1)
  go square(&wg, ch)

  // 10๋ฒˆ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์Œ
  // square ๋‚ด์—์„œ ์ฑ„๋„ ๋ฐ์ดํ„ฐ ๊ณ„์† ๊ธฐ๋‹ค๋ฆผ
  for i :=0; i< 10; i++ {
    ch <- i * 2
  }

  // ์ž‘์—…์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€๋งŒ,
  // square() ๋‚ด์—์„œ wg.Done()์ด ์‹คํ–‰ ๋˜์ง€ ์•Š๊ณ  deadlock๋ฐœ์ƒ
  // ํ•˜์ง€๋งŒ ์ฑ„๋„์„ ๋‹ซ์•„์„œ ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ ๊ฐ€๋Šฅ
  // ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋นผ๋‚ธ ์ƒํƒœ์ด๊ณ , ์ฑ„๋„์ด ๋‹ซํ˜”์œผ๋ฉด
  // for range ๋ฌธ์„ ๋น ์ ธ๋‚˜๊ฐ€๊ฒŒ ๋จ -> wg.Done()์ด ์‹คํ–‰๋จ!!
  close(ch)

  wg.Wait()
}

โ†‘ Back to top

SELECT statement

  • ์—ฌ๋Ÿฌ ์ฑ„๋„์„ ๋™์‹œ์— ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์Œ.
  • ์–ด๋–ค ์ฑ„๋„์ด๋ผ๋„ ํ•˜๋‚˜์˜ ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๋ฉด ํ•ด๋‹น ๊ตฌ๋ฌธ์„ ์‹คํ–‰ํ•˜๊ณ  select๋ฌธ์ด ์ข…๋ฃŒ๋จ.
  • ํ•˜๋‚˜์˜ case๋งŒ ์ฒ˜๋ฆฌ๋˜๋ฉด ์ข…๋ฃŒ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ˜๋ณตํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด for ๋ฌธ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ ํ•ด์•ผํ•จ
  • ์ฑ„๋„์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ค๊ธธ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋Œ€์‹ , ๋‹ค๋ฅธ์ž‘์—… ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜, ์—ฌ๋Ÿฌ์ฑ„๋„ ๋™์‹œ๋Œ€๊ธฐ
  • ์—ฌ๋Ÿฌ๊ฐœ ์ฑ„๋„์„ ๋™์‹œ์— ๊ธฐ๋‹ค๋ฆผ. ํ•˜๋‚˜์˜ ์ผ€์ด์Šค๋งŒ ์ฒ˜๋ฆฌ๋˜๋ฉด ์ข…๋ฃŒ๋จ
  • ๋ฐ˜๋ณต๋œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ ค๋ฉด for๋ฌธ๋„ ๊ฐ™์ด ์‚ฌ์šฉ
  • ch์ฑ„๋„๊ณผ quit์ฑ„๋„์„ ๋ชจ๋‘ ๊ธฐ๋‹ค๋ฆผ, ch์ฑ„๋„๋จผ์ € ์‹œ๋„ํ•˜๊ธฐ ๋•Œ๋ฌธ์—
  • ch์ฑ„๋„ ๋จผ์ € ์ฝ๋‹ค๊ฐ€ ๋ชจ๋‘ ์ฝ๊ณ ๋‚˜์„œ quit์— true๊ฐ€ ๋“ค์–ด์™€์„œ ์ฝ์œผ๋ฉด์„œ return ์‹คํ–‰
package main
import (
  "fmt"
  "sync"
  "time"
)

func square(wg *sync.WaitGroup, ch chan int, quit chan bool) {
  for {
    // ch์™€ quit ์–‘์ชฝ์„ ๋ชจ๋‘๊ธฐ๋‹ค๋ฆผ
    //    ch์ฑ„๋„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ์ฝ์œผ๋ฉด,
    //    quit ์ฑ„๋„๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ , square() ํ•จ์ˆ˜๊ฐ€ ์ข…๋ฃŒ๋จ
    select {
    case n := <-ch: // ch ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ๋‚ผ ์ˆ˜ ์žˆ์„ ๋•Œ ์‹คํ–‰
      fmt.Printf("Squared: %d\n", n*n)
      time.Sleep(time.Second)
    case <- quit: // quit ์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ๋‚ผ ์ˆ˜ ์žˆ์„ ๋•Œ ์‹คํ–‰
      wg.Done()
      return
    }
  }
}

func main() {
  var wg sync.WaitGroup
  ch := make(chan int)
  quit := make(chan bool)

  wg.Add(1)
  go square(&wg, ch, quit)

  for i:=0; i<10; i++ {
    ch <- i
  }

  quit <- true
  wg.Wait()
}

โ†‘ Back to top

Execute at Regular Intervals

package main

import (
  "fmt"
  "sync"
  "time"
)

func square(wg *sync.WaitGroup, ch chan int) {
  // ์›ํ•˜๋Š” ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์œผ๋กœ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” ์ฑ„๋„์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
  // 1์ดˆ๊ฐ„๊ฒฉ ์ฃผ๊ธฐ๋กœ ์‹œ๊ทธ๋„ ๋ณด๋‚ด์ฃผ๋Š” '์ฑ„๋„' ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜
  // func Tick(d Duration) <-chan Time
  // ์ด์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๋ฉด ์ผ์ • ์‹œ๊ฐ„๊ฐ„๊ฒฉ์œผ๋กœ ํ˜„์žฌ ์‹œ๊ฐ์„ ๋‚˜ํƒ€๋‚ด๋Š” Timer ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
  tick := time.Tick(time.Second)  // 1์ดˆ ๊ฐ„๊ฒฉ ์‹œ๊ทธ๋„

  // func After(d Duration) <-chan Time
  //    waits for the duration to elapse 
  //    and then sends the current time on the returned channel.
  // ์ด์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์œผ๋ฉด ์ผ์ •์‹œ๊ฐ„ ๊ฒฝ๊ณผ ํ›„์— ํ˜„์žฌ์‹œ๊ฐ์„ ๋‚˜ํƒ€๋‚ด๋Š” Timer ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
  terminate := time.After(10*time.Second) // 10์ดˆ ์ดํ›„ ์‹œ๊ทธ๋„

  for {
    select {
    case <- tick:
      fmt.Println("tick")
    case <- terminate:
      fmt.Println("terminated...")
      wg.Done()
      return
    case n := <-ch:
      fmt.Printf("Squared: %d\n", n*n)
      time.Sleep(time.Second)
    }
  }
}

func main() {
  var wg sync.WaitGroup
  ch := make(chan int)

  wg.Add(1)
  go square(&wg, ch)

  for i:=0; i<10; i++ {
    ch <-i
  }
  wg.Wait()
}

SELECT pattern

1-1. switch

func main() {
  i:= "korea"
  switch(i) {
  case "korea":
    fmt.Println("korea")
  case "usa":
    fmt.Println("usa")
  case "japan":
    fmt.Println("japan")
  }
}

1-2. switch

func main() {
  t:= time.Now()
  switch i {
  case t.Hour() < 12:
    fmt.Println("It's before noon")
  default:
    fmt.Println("It's after noon")
  }
}

1-3. switch

func WhiteSpace(c rune) bool {
  switch c {
    case ' ', '\t', '\n', '\f', '\r':
      return true
  }
  return false
}

1-4. switch

func main() {

Loop:
  for _, ch := range "a b\nc" {
    switch ch {
      case ' ':
        break
      case '\n':
        break Loop
      default:
        fmt.Println("%c\n", ch)
    }
  }
}

1-1. select

  • case๋ฌธ์˜ ์ฑ„๋„์— ๊ฐ’์ด ๋“ค์–ด์˜ฌ ๋•Œ๊นŒ์ง€ select๋ฌธ์—์„œ block ๋จ
  • c1 ์ฑ„๋„ ๊ฐ’์ด ์—†์œผ๋ฉด c2 ํ”„๋ฆฐํŠธ
  • c1, c2 ๋‘˜๋‹ค ์—†์œผ๋ฉด, default ํ”„๋ฆฐํŠธ
  • default ์ผ€์ด์Šค ์ •์˜ ์•ˆํ•˜๋ฉด select๋ฌธ block๋จ
package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        for {
            time.Sleep(2 * time.Second)
            c1 <- "one"
        }
    }()

    go func() {
        for {
            time.Sleep(4 * time.Second)
            c2 <- "two"
        }
    }()

    for {
        select {
        case r1 := <-c1:
            fmt.Printf("received: %s\n", r1)
        case r2 := <-c2:
            fmt.Printf("received: %s\n", r2)
        default:
            time.Sleep(1 * time.Second)
            fmt.Printf("--default--\n")
        }
    }
}

1-2. select

  • ์ƒ์‚ฐ์ž๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์ค„ ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ฐฉ์‹.
  • ๊ฒฐ๊ณผ ๋ฐ›์œผ๋ฉด return
package main

import (
    "fmt"
    "time"
)

func process(ch chan string) {
    time.Sleep(10 * time.Second)
    ch <- "process successful"
}

func scheduling() {
    //do something
}
func main() {
    ch := make(chan string)
    go process(ch)
    for {
        time.Sleep(1 * time.Second)
        select {
        case v := <-ch:
            fmt.Println("received value: ", v)
            return
        default:
            fmt.Println("no value received")
        }

        scheduling()
    }
}

1-3. select

  • case s1 ์ด ์„ ํƒ๋ ์ง€, s2๊ฐ€ ์„ ํƒ๋ ์ง€๋Š” ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๋žœ๋ค ์„ ํƒ์œผ๋กœ ์‚ฌ์šฉ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
package main

func server1(ch chan string) {
    ch <- "from server1"
}

func server2(ch chan string) {
    ch <- "from server2"
}

func main() {
  output1 := make(chan string)
  output2 := make(chan string)

  go server1(output1)
  go server2(output2)
  time.Sleep(1 * time.Second)

  select {
  case s1 := <-output1:
    fmt.Println(s1)
  case s2 := <-output2:
    fmt.Println(s2)
  }
}

1-4. select

  • 5์ดˆ ์ด๋‚ด ์ด๋ฆ„ ์ž…๋ ฅ๋ฐ›๊ธฐ
package main


func main() {

}

โ†‘ Back to top

Implementing Producer-Consumer Pattern with Channel

  • ์—ญํ•  ๋‚˜๋ˆ„๋Š” ๋ฐฉ๋ฒ•
  • ์ปจ๋ฒ ์ด์–ด๋ฒจํŠธ: ์ฐจ์ฒด์ƒ์‚ฐ->๋ฐ”ํ€ด์„ค์น˜->๋„์ƒ‰->์™„์„ฑ
package main

import (
  "fmt"
  "sync"
  "time"
)

// ๊ณต์ •์ˆœ์„œ:
//     Body -> Tire -> Color
// ์ƒ์‚ฐ์ž ์†Œ๋น„์ž ํŒจํ„ด (Producer Consumer Pattern) ๋˜๋Š” pipeline pattern
// MakeBody()๋ฃจํ‹ด์ด ์ƒ์‚ฐ์ž, InstallTire()๋ฃจํ‹ด์€ ์†Œ๋น„์ž
// InstallTire()๋Š” PaintCar()๋ฃจํ‹ด์— ๋Œ€ํ•ด์„œ๋Š” ์ƒ์‚ฐ์ž
type Car struct {
  Body string
  Tire string
  Color string
}

var wg sync.WaitGroup
var startTime = time.Now()

// 1์ดˆ๊ฐ„๊ฒฉ ์ฐจ์ฒด์ƒ์‚ฐํ•˜์—ฌ tireCh ์ฑ„๋„์— ๋ฐ์ดํ„ฐ ๋„ฃ์Œ
// 10์ดˆ ํ›„ tireCh ์ฑ„๋„ ๋‹ซ๊ณ  ๋ฃจํ‹ด์ข…๋ฃŒ
func MakeBody(tireCh chan *Car) { // ์ฐจ์ฒด์ƒ์‚ฐ
  tick := time.Tick(time.Second)
  after := time.After(10 * time.Second)

  for {
    select {
    case <- tick:
      // Make a body
      car := &Car{}
      car.Body = "Sports car"
      tireCh <- car
    case <-after:
      close(tireCh)
      wg.Done()
      return
    }
  }

}

// tireCh์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ ์ฝ์–ด์„œ ๋ฐ”ํ€ด์„ค์น˜ํ•˜๊ณ  paintCh์ฑ„๋„์— ๋„ฃ์–ด์คŒ
// tireCh์ฑ„๋„ ๋‹ซํžˆ๋ฉด ๋ฃจํ‹ด์ข…๋ฃŒํ•˜๊ณ  paintCh์ฑ„๋„ ๋‹ซ์•„์คŒ
func InstallTire(tireCh, paintCh chan *Car) { // ๋ฐ”ํ€ด์„ค์น˜
    for car := range tireCh {
      // Make a body
      time.Sleep(time.Second)
      car.Tire = "Winter tire"
      paintCh <- car
    }
    wg.Done()
    close(paintCh)
}


// paintCh์ฑ„๋„์—์„œ ๋ฐ์ดํ„ฐ ์ฝ์–ด์„œ ๋„์ƒ‰์„ ํ•˜๊ณ , ์™„์„ฑ๋œ ์ฐจ ์ถœ๋ ฅ
func PaintCar(paintCh chan *Car) { // ๋„์ƒ‰
  for car := range paintCh {
    // Make a body
    time.Sleep(time.Second)
    car.Color = "Red"
    duration := time.Now().Sub(startTime) // ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ์ถœ๋ ฅ
    fmt.Printf("%.2f Complete Car: %s %s %s\n",duration.Seconds(), car.Body, car.Tire, car.Color)
  }

  wg.Done()
}

func main() {
  tireCh := make(chan *Car)
  paintCh := make(chan *Car)

  fmt.Println("Start the factory")

  wg.Add(3)
  go MakeBody(tireCh)
  go InstallTire(tireCh, paintCh)
  go PaintCar(paintCh)

  wg.Wait()
  fmt.Println("Close the factory")
}

โ†‘ Back to top

mutex to ensure atomic access to a shared variable

A mutex helps achieve atomic access by allowing only one thread to hold the lock (mutex) at any given time.

package main

import (
    "fmt"
    "sync"
)

type counter struct {
    i  int64
    wg sync.WaitGroup
    mu sync.Mutex
}

func main() {
    c := counter{i: 0}

    for i := 0; i < 1000; i++ {
        c.wg.Add(1)
        go func(num int) {
            defer c.wg.Done()
            c.mu.Lock()
            c.i += 1
            c.mu.Unlock()
        }(i)
    }

    c.wg.Wait()

    fmt.Println("Final Counter Value:", c.i)
}

โ†‘ Back to top

buffered vs. unbuffered channel

If you don't explicitly close an unbuffered (non-buffered) channel in Go, it won't cause any immediate issues. However, there are important implications:

  1. Blocking Behavior:
  2. If a goroutine tries to receive from an unbuffered channel and no other goroutine is sending to it, the receiver will block indefinitely.
  3. Similarly, if a goroutine tries to send to an unbuffered channel and no other goroutine is receiving from it, the sender will block until another goroutine starts receiving.

  4. Resource Leaks:

  5. If you forget to close an unbuffered channel, it remains open indefinitely.
  6. This can lead to resource leaks, especially if the channel is used for synchronization or signaling.
  7. Properly closing channels ensures that goroutines can exit gracefully when their work is done.

  8. Signaling Completion:

  9. Closing an unbuffered channel is often used to signal the end of communication between goroutines.
  10. It allows the receiving goroutine to know that no more values will be sent.
  11. When the sender closes the channel, the receiver can detect this and exit gracefully.

In summary, while not explicitly closing an unbuffered channel won't cause immediate errors, it's good practice to close channels when they are no longer needed. This helps prevent blocking issues and ensures proper resource management. ๐Ÿ˜Š

โ†‘ Back to top

Context

  • https://go.dev/doc/database/cancel-operations

  • ์ปจํ…์ŠคํŠธ ์‚ฌ์šฉํ•˜๊ธฐ

  • ์ปจํ…์ŠคํŠธ๋Š” ์ž‘์—…์„ ์ง€์‹œํ• ๋•Œ ์ž‘์—…๊ฐ€๋Šฅ ์‹œ๊ฐ„, ์ž‘์—… ์ทจ์†Œ ๋“ฑ์˜ ์กฐ๊ฑด์„ ์ง€์‹œํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์—…๋ช…์„ธ์„œ ์—ญํ• 
  • ์ƒˆ๋กœ์šด ๊ณ ๋ฃจํ‹ด์œผ๋กœ ์ž‘์—… ์‹œ์ž‘ํ• ๋–„ ์ผ์ •์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ์ž‘์—…์ง€์‹œํ•˜๊ฑฐ๋‚˜, ์™ธ๋ถ€์—์„œ ์ž‘์—… ์ทจ์†Œ์‹œ ์‚ฌ์šฉ.
  • ์ž‘์—… ์„ค์ •์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋„ ์ „๋‹ฌ ๊ฐ€๋Šฅ
  • The context package provides tools for managing concurrent operations.
  • It allows you to control the lifecycle, cancellation, and propagation of requests across multiple Goroutines.
  • Key features:
    • Cancellation: Propagate cancellation signals across Goroutines.
    • Deadlines: Set deadlines for operations.
    • Values: Pass request-scoped data down the chain.
  • Example: Managing timeouts for API requests using context.WithTimeout.
package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    urls := []string{
        "https://api.example.com/users",
        "https://api.example.com/products",
        "https://api.example.com/orders",
    }

    results := make(chan string)

    for _, url := range urls {
        go fetchAPI(ctx, url, results)
    }

    for range urls {
        fmt.Println(<-results)
    }
}

func fetchAPI(ctx context.Context, url string, results chan<- string) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        results <- fmt.Sprintf("Error creating request for %s: %s", url, err.Error())
        return
    }

    client := http.DefaultClient
    resp, err := client.Do(req)
    if err != nil {
        results <- fmt.Sprintf("Error making request to %s: %s", url, err.Error())
        return
    }
    defer resp.Body.Close()

    results <- fmt.Sprintf("Response from %s: %d", url, resp.StatusCode)
}

โ†‘ Back to top

Cancelable Context

  • ์ด ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด, ์ž‘์—…์ž์—๊ฒŒ ์ „๋‹ฌํ•˜๋ฉด ์ž‘์—… ์ง€์‹œํ•œ ์ง€์‹œ์ž๊ฐ€ ์›ํ• ๋•Œ ์ž‘์—…์ทจ์†Œ ์•Œ๋ฆด ์ˆ˜ ์žˆ์Œ
package main

import (
  "fmt"
  "sync"
  "time"
  "context"
)

var wg sync.WaitGroup

// ์ž‘์—…์ด ์ทจ์†Œ๋  ๋•Œ๊นŒ์ง€ 1์ดˆ๋งˆ๋‹ค ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅํ•˜๋Š” ๊ณ ๋ฃจํ‹ด
func PrintEverySecond(ctx context.Context) {
  tick := time.Tick(time.Second)
  for {
    select {
    case <-ctx.Done():
      wg.Done()
      return
    case <-tick:
      fmt.Println("tick")
    }
  }
}

func main() {
  wg.Add(1)

  // ์ทจ์†Œ ๊ฐ€๋Šฅํ•œ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ : ์ปจํ…์ŠคํŠธ ๊ฐœ์ฒด์™€ ์ทจ์†Œํ•จ์ˆ˜ ๋ฐ˜ํ™˜
  ctx, cancel := context.WithCancel(context.Background())

  go PrintEverySecond(ctx)
  time.Sleep(5 * time.Second)

  // ์ž‘์—…์ทจ์†Œ
  //     ์ปจํ…์ŠคํŠธ์˜ Done()์ฑ„๋„์— ์‹œ๊ทธ๋„์„ ๋ณด๋‚ด, ์ž‘์—…์ž๊ฐ€ ์ž‘์—… ์ทจ์†Œํ•˜๋„๋ก ์•Œ๋ฆผ
  //    <-ctx.Done() ์ฑ„๋„
  cancel()

  wg.Wait()
}

โ†‘ Back to top

Setting a Timeout in Context

  • ์ผ์ •์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ์ž‘์—…์„ ์ง€์‹œํ•  ์ˆ˜ ์žˆ๋Š” ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
  • WithTimeout() ๋‘๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜๋ฉด, ๊ทธ์‹œ๊ฐ„์ด ์ง€๋‚œ ๋’ค
  • ์ปจํ…์ŠคํŠธ์˜ Done()์ฑ„๋„์— ์‹œ๊ทธ๋„์„ ๋ณด๋‚ด์„œ ์ž‘์—… ์ข…๋ฃŒ
  • WithTimeout() ์—ญ์‹œ ๋‘๋ฒˆ์งธ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ cancelํ•จ์ˆ˜ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—
  • ์ž‘์—… ์‹œ์ž‘์ „์— ์›ํ•˜๋ฉด ์–ธ์ œ๋“ ์ง€ ์ž‘์—… ์ทจ์†Œ ๊ฐ€๋Šฅ
func main() {
  wg.Add(1)

  // 5์ดˆ ํ›„ ์ปจํ…์ŠคํŠธ์˜ Done()์ฑ„๋„์— ์‹œ๊ทธ๋„์„ ๋ณด๋‚ด ์ž‘์—…์ข…๋ฃŒ ์š”์ฒญ
  ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
  go PrintEverySecond(ctx)

  wg.Wait()
}

โ†‘ Back to top

Setting a Specific Value in Context

  • ์ž‘์—…์ž์—๊ฒŒ ์ง€์‹œํ• ๋•Œ ๋ณ„๋„์˜ ์ง€์‹œ์‚ฌํ•ญ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
  • ์ปจํ…์ŠคํŠธ์— ํŠน์ •ํ‚ค๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ค๋„๋ก ์„ค์ • ๊ฐ€๋Šฅ
  • context.WithValue()๋กœ ์ปจํ…์ŠคํŠธ์— ๊ฐ’ ์„ค์ • ๊ฐ€๋Šฅ
package main

import (
  "fmt"
  "sync"
  "context"
)

var wg sync.WaitGroup

func square(ctx context.Context) {
  // ์ปจํ…์ŠคํŠธ์—์„œ ๊ฐ’์„ ์ฝ๊ธฐ
  // Value๋Š” ๋นˆ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์ด๋ฏ€๋กœ (int)๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ n์— ํ• ๋‹น
  if v:= ctx.Value("number"); v != nil {
    n := v.(int)
    fmt.Printf("Square:%d\n", n*n)
  }

  wg.Done()
}

func main() {
  wg.Add(1)

  // "number"๋ฅผ ํ‚ค๋กœ ๊ฐ’์„ 9๋กœ ์„ค์ •ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ฆ
  // square์˜ ์ธ์ˆ˜๋กœ ๋„˜๊ฒจ์„œ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
  ctx := context.WithValue(context.Background(), "number", 9)

  go square(ctx)

  wg.Wait()
}

โ†‘ Back to top

Creating a Context with Both Cancellation and Value

  • ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๋•Œ ํ•ญ์ƒ ์ƒ์œ„ ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ ์ธ์ˆ˜๋กœ ๋„ฃ์–ด์ค˜์•ผ ํ–ˆ์Œ
  • ์ผ๋ฐ˜์ ์œผ๋กœ context.Background()๋ฅผ ๋„ฃ์–ด์คฌ๋Š”๋ฐ, ์—ฌ๊ธฐ์— ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด ๋„ฃ์–ด๋„ ๋จ
  • ์ด๋ฅผํ†ตํ•ด ์—ฌ๋Ÿฌ ๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, ๊ธฐ๋Šฅ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ
  • ๊ตฌ๊ธ€: "golang concurrency patterns"
ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, "number", 9)
ctx = context.WithValue(ctx, "keyword", "Lilly")

โ†‘ Back to top