수색…
소개
Go에서는 동시성이 goroutines의 사용을 통해 이루어지며 goroutine 간의 통신은 일반적으로 채널을 통해 수행됩니다. 그러나 뮤텍스 및 대기 그룹과 같은 다른 동기화 수단을 사용할 수 있으므로 채널보다 편리 할 때마다 사용해야합니다.
통사론
- doWork () // doWork를 goroutine으로 실행한다.
- ch : = make (chan int) // int 형의 새로운 채널을 선언한다.
- ch <- 1 // 채널로 전송
- value = <-ch // 채널로부터 수신
비고
Go의 Goroutines는 사용 측면에서 다른 언어의 스레드와 유사합니다. 내부적으로 Go는 여러 개의 스레드 ( GOMAXPROCS
로 GOMAXPROCS
)를 만든 다음 goroutines가 스레드에서 실행되도록 예약합니다. 이러한 설계로 인해 Go의 동시성 메커니즘은 메모리 사용 및 초기화 시간면에서 스레드보다 훨씬 효율적입니다.
goroutines 만들기
모든 함수는 호출에 키워드 go
접두어로 붙임으로써 goroutine으로 호출 할 수 있습니다.
func DoMultiply(x,y int) {
// Simulate some hard work
time.Sleep(time.Second * 1)
fmt.Printf("Result: %d\n", x * y)
}
go DoMultiply(1,2) // first execution, non-blocking
go DoMultiply(3,4) // second execution, also non-blocking
// Results are printed after a single second only,
// not 2 seconds because they execute concurrently:
// Result: 2
// Result: 12
함수의 반환 값은 무시됩니다.
Hello World Goroutine
단일 채널, 단일 goroutine, 하나의 쓰기, 하나의 읽기.
package main
import "fmt"
import "time"
func main() {
// create new channel of type string
ch := make(chan string)
// start new anonymous goroutine
go func() {
time.Sleep(time.Second)
// send "Hello World" to channel
ch <- "Hello World"
}()
// read from channel
msg, ok := <-ch
fmt.Printf("msg='%s', ok='%v'\n", msg, ok)
}
채널 ch
는 비 버퍼 또는 동기 채널 입니다.
time.Sleep
은 main()
함수가 ch
채널에서 기다릴 것을 설명합니다. 즉, goroutine으로 실행 된 함수 리터럴 은 해당 채널을 통해 값을 보내는 시간을가집니다. 수신 연산자 <-ch
는 실행을 차단합니다. main()
. 그렇지 않으면 main()
종료 될 때 goroutine이 종료되고 값을 보낼 시간이 없습니다.
goroutines를 기다리는 중
Go 프로그램은 main
함수가 끝날 때 끝납니다 . 따라서 모든 goroutines이 끝날 때까지 기다리는 것이 일반적입니다. 이에 대한 일반적인 해결책은 sync.WaitGroup 객체를 사용하는 것 입니다.
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // 1
func routine(i int) {
defer wg.Done() // 3
fmt.Printf("routine %v finished\n", i)
}
func main() {
wg.Add(10) // 2
for i := 0; i < 10; i++ {
go routine(i) // *
}
wg.Wait() // 4
fmt.Println("main finished")
}
실행 순서에 따라 WaitGroup 사용 :
- 전역 변수의 선언. 이를 전역으로 만드는 것이 모든 함수와 메소드에 표시 할 수있는 가장 쉬운 방법입니다.
- 카운터를 늘리십시오. 새로운 goroutine이 메모리 모델 보증 으로 인해 4 이전에 실행된다는 보장이 없기 때문에 이것은 메인 goroutine에서 수행되어야합니다.
- 카운터를 줄입니다. 이것은 goroutine의 출구에서 이루어져야합니다. 지연 호출을 사용하여 함수의 종료 와 상관없이 함수가 끝날 때마다 호출되도록합니다 .
- 카운터가 0이 될 때까지 기다립니다. 모든 goroutines이 완료되기 전에 프로그램이 종료되지 않도록 메인 goroutine에서 수행해야합니다.
* 매개 변수는 새 goroutine을 시작하기 전에 평가 됩니다. 따라서 wg.Add(10)
전에 명시 적으로 값을 정의하여 패닉 코드로 인해 카운터가 증가하지 않도록해야합니다. WaitGroup 10 개 항목 추가, 그래서 전에 10 개 항목에 대한 대기 wg.Wait
다시로 제어를 돌려 main()
goroutine. 여기서 i 값은 for 루프에 정의됩니다.
루프에서 goroutine과 함께 closure 사용하기
루프에서 다음 예제의 루프 변수 (val)는 루프를 통과하면서 값을 변경하는 단일 변수입니다. 따라서 실제로 각 값의 값을 goroutine에 전달하려면 다음을 수행해야합니다.
for val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}
val
을 넘기지 않고 func(val interface{}) { ... }()
를 수행하면 goroutine이 실제로 실행될 때 val의 값이됩니다.
동일한 효과를 얻는 또 다른 방법은 다음과 같습니다.
for val := range values {
val := val
go func() {
fmt.Println(val)
}()
}
이상한 모양의 val := val
은 각 반복마다 새로운 변수를 생성 한 다음 goroutine에 의해 액세스됩니다.
goroutines 중지
package main
import (
"log"
"sync"
"time"
)
func main() {
// The WaitGroup lets the main goroutine wait for all other goroutines
// to terminate. However, this is no implicit in Go. The WaitGroup must
// be explicitely incremented prior to the execution of any goroutine
// (i.e. before the `go` keyword) and it must be decremented by calling
// wg.Done() at the end of every goroutine (typically via the `defer` keyword).
wg := sync.WaitGroup{}
// The stop channel is an unbuffered channel that is closed when the main
// thread wants all other goroutines to terminate (there is no way to
// interrupt another goroutine in Go). Each goroutine must multiplex its
// work with the stop channel to guarantee liveness.
stopCh := make(chan struct{})
for i := 0; i < 5; i++ {
// It is important that the WaitGroup is incremented before we start
// the goroutine (and not within the goroutine) because the scheduler
// makes no guarantee that the goroutine starts execution prior to
// the main goroutine calling wg.Wait().
wg.Add(1)
go func(i int, stopCh <-chan struct{}) {
// The defer keyword guarantees that the WaitGroup count is
// decremented when the goroutine exits.
defer wg.Done()
log.Printf("started goroutine %d", i)
select {
// Since we never send empty structs on this channel we can
// take the return of a receive on the channel to mean that the
// channel has been closed (recall that receive never blocks on
// closed channels).
case <-stopCh:
log.Printf("stopped goroutine %d", i)
}
}(i, stopCh)
}
time.Sleep(time.Second * 5)
close(stopCh)
log.Printf("stopping goroutines")
wg.Wait()
log.Printf("all goroutines stopped")
}
두 개의 goroutines가있는 탁구
package main
import (
"fmt"
"time"
)
// The pinger prints a ping and waits for a pong
func pinger(pinger <-chan int, ponger chan<- int) {
for {
<-pinger
fmt.Println("ping")
time.Sleep(time.Second)
ponger <- 1
}
}
// The ponger prints a pong and waits for a ping
func ponger(pinger chan<- int, ponger <-chan int) {
for {
<-ponger
fmt.Println("pong")
time.Sleep(time.Second)
pinger <- 1
}
}
func main() {
ping := make(chan int)
pong := make(chan int)
go pinger(ping, pong)
go ponger(ping, pong)
// The main goroutine starts the ping/pong by sending into the ping channel
ping <- 1
for {
// Block the main thread until an interrupt
time.Sleep(time.Second)
}
}
Go Playground에서이 코드를 약간 수정 한 버전을 실행하십시오.