Golang minimalism tutorial (iii) : concurrency support

  • 2020-05-07 19:52:42
  • OfStack

The Golang runtime (runtime) manages a class of lightweight threads called goroutine. There is no problem creating 100,000 levels of goroutine. Example:


package main
 
import (
    "fmt"
    "time"
)
 
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
 
func main() {
    // open 1 a goroutine perform say function
    go say("world")
    say("hello")
}

We use channel and goroutine for communication. channel is a channel with a type that is used to receive and send specific types of values. The operator < - called the channel operator (the arrow in this operator indicates the direction of the value) :


// send v to channel ch
ch <- v
// receive channel ch And assign a value to v
v := <-ch

Using channel and goroutine communications avoids the explicit use of the lock mechanism, which by default blocks when sending and receiving values through channel.

Create channel by make function:


// int The specified channel The type of send/receive value is int
ch := make(chan int)

1 complete example:


package main
 
import "fmt"
 
// Calculate the array a The sum of the values of all elements
func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    // The calculated results are sent to channel c
    c <- sum
}
 
func main() {
    a := []int{7, 2, 8, -9, 4, 0}
 
    // create channel c
    c := make(chan int)
 
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
 
    // Receive two goroutine The calculated results sent
    x, y := <-c, <-c
 
    fmt.Println(x, y, x+y)
}package main
 
import "fmt"
 
// Calculate the array a The sum of the values of all elements
func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    // The calculated results are sent to channel c
    c <- sum
}
 
func main() {
    a := []int{7, 2, 8, -9, 4, 0}
 
    // create channel c
    c := make(chan int)
 
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
 
    // Receive two goroutine The calculated results sent
    x, y := <-c, <-c
 
    fmt.Println(x, y, x+y)
}

channel can cache the passed value with 1 buffer (buffer), which blocks when sent to channel only if the buffer is full, and when received from channel only if the buffer is empty:


package main
 
import "fmt"
 
func main() {
    // create channel , the buffer length is 2
    c := make(chan int, 2)
    // Due to the channel The buffer length of 2
    // So the send will not block
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}

The sender can call close to close channel, and the receiver can detect if channel is closed:


// Here, ok for false Means that there is no more value to receive, and channel Was closed
v, ok := <-ch

Do not send values to channel that have been turned off (will cause a panic).

We can use for range to receive the values in channel:


package main
 
import "fmt"
 
func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    // You have to shut it down c
    close(c)
}
 
func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    // Here, for and range Use a combination of
    // Constant receiving c The values in the 1 Until it's shut down
    for i := range c {
        fmt.Println(i)
    }
}

In general, we don't need to actively close channel. But there are times when the receiver must be told that there is no more value to receive, when active closure is necessary, such as ending the for range loop.

The select statement allows one goroutine to wait for multiple communication operations. select will block until a certain case can run. If there are more than one executable at the same time, one will be randomly selected:


package main
 
import "fmt"
 
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        // Controls this thread to exit
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
 
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

default in select is executed when no case is available (similar to switch) :


package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    // create 1 a tick channel
    // in 100 It's going to go in milliseconds tick channel Send the current time in
    tick := time.Tick(100 * time.Millisecond)
    // create 1 a boom channel
    // in 500 It's going to go in milliseconds boom channel Send the current time in
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}


Related articles: