Go Concurrency: Goroutine and Channel, Select, Lock
Introduction
- Concurrency programming refers to the presence of multiple independent execution units in a program that can execute concurrently, improving the efficiency of the program. Go is known for its high concurrency, and its concurrency programming model is based on goroutines, which are lightweight threads that can create thousands of goroutines in a program without exhausting system resources.
- Go's
goroutine
is a kind of coroutine, and the creation and destruction ofgoroutine
are very fast. The scheduling ofgoroutine
is automatically completed by the Go runtime system, and the programmer does not need to care about it. channel
is a data structure used for communication between goroutines in Go language.channel
is type-safesync
package provides some locking mechanisms, such assync.Mutex
,sync.RWMutex
,sync.WaitGroup
, etc., which can ensure the safe access of shared resources.
Goroutine
- The format of Go language goroutine is as follows:
go
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() {
go say("world")
say("hello")
}
In the above code, the say
function is a goroutine, go say("world")
creates a goroutine, and say("hello")
is the main goroutine.
Run the result
shell
hello
world
Perhaps this is not intuitive enough, we can output two strings multiple times in a loop to see the execution order of the goroutine.
go
func main() {
for i := 0; i < 5; i++ {
go say("world")
say("hello")
}
}
Run the result
shell
hello
world
hello
hello
world
world
hello
world
hello
world
As you can see, the execution order of the goroutine is not determined, because the scheduling of the goroutine is automatically completed by the Go runtime system.
Channel
- The channel in Go language is a type-safe data structure used for communication between goroutines.
Declaration
go
// var channel_name chan element_type
var intChan chan int
Create
- The creation format of a channel without a buffer is as follows:
go
// channel_name = make(chan element_type)
intChan = make(chan int)
Send and Receive
- The format of sending and receiving channels is as follows:
go
// Send
// channel_name <- element
intChan <- 1
// Receive
// element = <- channel_name
i := <-intChan
Buffer
- The buffer refers to the number of elements that can be stored in the channel. When the buffer is full, the send operation will block until there is space. When the buffer is empty, the receive operation will block until there are elements.
- The buffer size of the channel can be specified by the second parameter of the
make
function, which is 0 by default, for example:
go
intChan = make(chan int, 10)
Total Example
go
package main
import "fmt"
func routine1(c chan int) {
for i := 0; i < 10; i++ {
c <- i
}
close(c)
}
func routine2(c chan int) {
for {
i, ok := <-c
if !ok {
break
}
fmt.Println(i)
}
}
func main() {
c := make(chan int)
go routine1(c)
go routine2(c)
select {}
}
In the above code, the routine1
function sends integers from 0 to 9 to the channel c
, the routine2
function receives integers from the channel c
and prints them, and the select{}
is an empty select
statement used to block the main goroutine.
Run the result
shell
0
1
2
3
4
5
6
7
8
9
Select Statement
- The
select
statement is used to handle multiple channel send and receive operations. Theselect
statement randomly selects an available channel to execute. If there is no available channel, it will block. - The format of the
select
statement is as follows:
go
select {
case <-ch1:
// do something
case ch2 <- 1:
// do something
default:
// do something
}
Lock
- The
sync
package of Go language provides some locking mechanisms, such assync.Mutex
,sync.RWMutex
,sync.WaitGroup
, etc., which can ensure the safe access of shared resources.
Here is an example using sync.Mutex
:
go
package main
import (
"fmt"
"sync"
)
var count int
var lock sync.Mutex
func increment() {
lock.Lock()
defer lock.Unlock()
count++
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)x
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println(count)
}
Extension
- In addition to using locks, you can also use the
atomic
package to ensure the safe access of shared resources. This package ensures the atomicity of operations.
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var count int32
func increment() {
atomic.AddInt32(&count, 1)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println(count)
}
Summary
- Go language's concurrency programming model is based on goroutines, which are lightweight threads. Reasonable use of goroutines can improve the efficiency of the program.
- Channels are data structures used for communication between goroutines in Go language. Channels are type-safe
- The
sync
package provides some locking mechanisms, such assync.Mutex
,sync.RWMutex
,sync.WaitGroup
, etc., which can ensure the safe access of shared resources. - The
atomic
package can be used to ensure the safe access of shared resources. This package ensures the atomicity of operations.