수색…


소개

Go에서는 동시성이 goroutines의 사용을 통해 이루어지며 goroutine 간의 통신은 일반적으로 채널을 통해 수행됩니다. 그러나 뮤텍스 및 대기 그룹과 같은 다른 동기화 수단을 사용할 수 있으므로 채널보다 편리 할 때마다 사용해야합니다.

통사론

  • doWork () // doWork를 goroutine으로 실행한다.
  • ch : = make (chan int) // int 형의 새로운 채널을 선언한다.
  • ch <- 1 // 채널로 전송
  • value = <-ch // 채널로부터 수신

비고

Go의 Goroutines는 사용 측면에서 다른 언어의 스레드와 유사합니다. 내부적으로 Go는 여러 개의 스레드 ( GOMAXPROCSGOMAXPROCS )를 만든 다음 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.Sleepmain() 함수가 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 사용 :

  1. 전역 변수의 선언. 이를 전역으로 만드는 것이 모든 함수와 메소드에 표시 할 수있는 가장 쉬운 방법입니다.
  2. 카운터를 늘리십시오. 새로운 goroutine이 메모리 모델 보증 으로 인해 4 이전에 실행된다는 보장이 없기 때문에 이것은 메인 goroutine에서 수행되어야합니다.
  3. 카운터를 줄입니다. 이것은 goroutine의 출구에서 이루어져야합니다. 지연 호출을 사용하여 함수의 종료 와 상관없이 함수가 끝날 때마다 호출되도록합니다 .
  4. 카운터가 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에서이 코드를 약간 수정 한 버전을 실행하십시오.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow