Go语言的调度模型被称为GMP,这是一个高效且复杂的调度系统,用于在可用的物理线程上调度goroutines(Go的轻量级线程)。GMP模型由三个主要组件构成:Goroutine、M(机器)和P(处理器)。下面详细介绍这三个组件以及它们如何协同工作。
package main
import (
"fmt"
"sync"
)
// 多协程计算阶乘
var (
myMap = make(map[int]int) // 全局变量
mu sync.Mutex // 安全访问myMap
wg sync.WaitGroup // 等待所有协程完成
)
func main() {
// 开启协程
for i := 1; i <= 10; i++ {
wg.Add(1)
go factorial(i)
}
// 等待所有协程完成后再打印
wg.Wait()
// 遍历myMap并打印结果
for i, v := range myMap {
fmt.Println(i, v)
}
}
// 计算阶乘
func factorial(n int) {
res := 1
// 计算阶乘
for i := 1; i <= n; i++ {
res *= i
}
// 递延地减少WaitGroup计数器
defer wg.Done()
// 在修改myMap之前进行互斥锁操作
mu.Lock()
myMap[n] = res
mu.Unlock()
}
Go语言的并发模型被称为MPG模型,其中:
当一个Go程序运行时:
main
函数启动多个goroutines时,这些G被分配到不同的P上,并且可能在不同的M上执行。wg.Wait()
返回),程序进入最后阶段,遍历并打印myMap
中存储的结果。make
关键字创建管道。可以创建有缓冲的管道或无缓冲的管道。ch := make(chan int)
创建一个传递整型数据的无缓冲管道。ch <- v
)会阻塞,直到另一端有goroutine准备好接收(<-ch
)。无缓冲管道确保同时只有一个数据在通道中传递,它强制发送者和接收者同步交换数据。<-
)来发送和接收数据。ch <- v
表示将值v
发送到管道ch
,v := <-ch
表示从管道ch
接收值并赋给变量v
。安全性:管道在内部实现了必要的同步机制,因此在多个goroutines访问时是安全的。
阻塞性:无缓冲管道在发送或接收时会阻塞,直到另一端准备好。
关闭管道:
使用close
函数关闭管道。
关闭管道后,不能再向管道发送数据,但仍可以接收管道中已存在的数据。
尝试向已关闭的管道发送数据会引发panic。
范围循环:可以使用for range
循环从管道接收数据,直到管道被关闭。
package main
import (
"fmt"
"sync"
)
var (
ch = make(chan int) // FIFO 队列 first in first out 线程安全
wg2 sync.WaitGroup // 用于等待所有goroutine完成
)
func main() {
for i := 1; i <= 10; i++ {
// 添加WaitGroup的计数
wg2.Add(1)
go calChannel(i)
}
wg2.Wait() // 等待所有goroutine完成
close(ch) // 关闭通道
// 启动一个新的goroutine来打印管道中的值
go func() {
for v := range ch {
fmt.Println(v)
}
}()
}
func calChannel(n int) {
defer wg2.Done() // 在函数退出时通知WaitGroup
// 通过通道计算阶乘
res := 1
for i := 1; i <= n; i++ {
res *= i
}
ch <- res
}
package main
import "fmt"
func main() {
ch := make(chan interface{}, 3)
ch <- 88
ch <- "i am god"
cat := Cat{Name: "小花猫", Age: 4}
ch <- cat
close(ch)
// 丢弃管道中的值
<-ch
<-ch
v := <-ch
fmt.Printf("%T", v)
fmt.Println()
// 需要类型断言 ∵从管道中取出的值类型实际是interface{}类型 只有空接口类型才可以类型断言
val := v.(Cat)
fmt.Printf("%v", val.Name)
}
type Cat struct {
Name string
Age int
}