好久没学习golang了,今天学习了一下协程。
go关键字
使用go关键字,再加一个函数名,就可以开启一个新的协程.
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("goroutine")
}()
fmt.Println("main")
time.Sleep(time.Second)
}
通道
通道是协程之间的通讯机制。通过<-运算符向通道写入或读取数据。
创建通道是make函数。从通道读数据时,如果通道没有数据,就会阻塞。
以下是一个简单的例子:
package main
import (
"fmt"
"time"
)
func worker(i int, ch chan int) {
for {
fmt.Println("worker", i, "通道数据", <-ch)
}
}
func main() {
// RoutineDemo()
ch := make(chan int)
// 开始3个协程
for i := 0; i < 3; i++ {
go worker(i, ch)
}
// 向通道写入数据10次
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(time.Second)
}
}
等待
在Go语言中,可以使用WaitGroup等待一组协程执行完毕。
前面的例子里,用的是time.Sleep函数,让主协程等待子协程执行完毕。这种写法肯定是不行的,因为很难准确估计子协程执行需要的时间。
幸好go语言提供了WaitGroup,可以很方便地等待所有子协程执行完毕。
package main
import (
"fmt"
"math/rand/v2"
"sync"
"time"
)
func work(i int, wg *sync.WaitGroup) {
var n = time.Duration(rand.IntN(3))
defer wg.Done()
time.Sleep(time.Second * n)
fmt.Println(i, "Hello World")
}
func main() {
wait := sync.WaitGroup{}
for i := 0; i < 5; i++ {
go work(i, &wait)
wait.Add(1)
}
wait.Wait()
fmt.Println("All Done")
}
互斥锁
在Go语言中,可以使用sync.Mutex互斥锁来控制对共享资源的访问。
在下面的例子中,如果不适用互斥锁,自增的结果就会不正确。
package main
import (
"fmt"
"sync"
)
var counter int
func increment(group *sync.WaitGroup, locker *sync.Mutex) {
locker.Lock()
counter++
group.Done()
locker.Unlock()
}
func main() {
var lock sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg, &lock)
}
wg.Wait()
fmt.Println(counter)
}
去掉互斥锁,我得到的结果是991,而不是1000。
选择器
Go语言中的select关键字可以用来监听通道上的数据流动。
当通道上有数据流入时,对应的case会被执行。如果没有数据流入,默认的代码会被执行。
比如说主协程可以监听多个通道,每个子协程向不同的通道写入数据。以下是一个简单的例子:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
for {
time.Sleep(1 * time.Second)
ch1 <- "hello"
}
}()
go func() {
for {
time.Sleep(2 * time.Second)
ch2 <- "world"
}
}()
for {
select {
case msg1 := <-ch1:
fmt.Println("msg1", msg1)
case msg2 := <-ch2:
fmt.Println("msg2", msg2)
}
}
}