고루틴과 동시성 프로그래밍

고루틴

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

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

// 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

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

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

고루틴의 동작방법

↑ Back to top

동시성 프로그래밍 주의점

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

뮤텍스를 이용한 동시성 문제 해결

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

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

뮤텍스의 문제점

  1. 뮤텍스는 동시성 프로그래밍 성능이점 감소시킴
  2. 데드락 발생 가능
    • e.g. 식탁에 A와 B가 각각 수저1, 포크1 집고있음.
    • A,B가 포크1, 수저1 집으려 할때, A,B 누구하나 양보하지 않아, 밥을 먹을 수 없음: 두개 mutex 각각 차지
    • 어떤 고루틴도 원하는 만큼 뮤텍스를 확보하지 못해서 무한히 대기하는 경우; 데드락
    • 멀티코어 환경에서는 여러 고루틴으로 성능 향상 가능
    • 같은메모리 접근시 꼬일 수 있음
    • 뮤텍스로 고루틴 하나만 접근하도록 하여 꼬이는 문제 해결 가능
    • 하지만, 뮤텍스를 잘못 사용하면 성능향상 없이 데드락 발생가능
      • 뮤텍스 사용시 좁은 범위에서 사용하여 데드락 발생 방지
      • 또는 둘다 수저-> 포크 순서로 뮤텍스 락 사용하면 해결 가능
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

또 다른 자원 관리 기법

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

채널

// 채널타입: 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)
    • 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)
    • 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

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

  1. 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

↑ Back to top

채널 크기

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

채널에서 데이터 대기

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 문

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

일정간격으로 실행

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 패턴

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

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

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

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

package main


func main() {

}

↑ Back to top

채널로 생산자 소비자 패턴 구현

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:
    • If a goroutine tries to receive from an unbuffered channel and no other goroutine is sending to it, the receiver will block indefinitely.
    • 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.
  2. Resource Leaks:
    • If you forget to close an unbuffered channel, it remains open indefinitely.
    • This can lead to resource leaks, especially if the channel is used for synchronization or signaling.
    • Properly closing channels ensures that goroutines can exit gracefully when their work is done.
  3. Signaling Completion:
    • Closing an unbuffered channel is often used to signal the end of communication between goroutines.
    • It allows the receiving goroutine to know that no more values will be sent.
    • 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

컨텍스트

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

작업취소 가능한 컨텍스트

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

작업시간 설정한 컨텍스트

func main() {
  wg.Add(1)

  // 5초 후 컨텍스트의 Done()채널에 시그널을 보내 작업종료 요청
  ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
  go PrintEverySecond(ctx)

  wg.Wait()
}

↑ Back to top

특정 값을 설정한 컨텍스트

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

취소도 되면서 값도 설정하는 컨텍스트 만들기

ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, "number", 9)
ctx = context.WithValue(ctx, "keyword", "Lilly")

↑ Back to top