https://golang.google.cn/
CSP基本思想是:
将并发系统抽象为channel和process两部分,二者相互独立,没有从属关系 ;
channel用来传递消息,消息的发送和接收有严格的时序限制;
process用于执行 。
在Go语言中,channel就是通道 process就是goroutine 。
goroutine的调度模型抽象出来三个实体:M P G
M:machine:一个内核线程
P:processor Go执行一段代码的上下文环境
G:goroutine 一个代码片段
MP(内核线程+上下文环境) 关联才形成一个有效的G运行环境
每个P包含一个可运行的G队列 runq
一个Go程序最大可以使用10000个M
runtime/debug SetMaxThreads 设置M最大值
调用时会返回旧的M个数,且新值小于旧值会panic
runtime.GOMAXPROCS
(新值小于旧值,不更新,只返回旧值)
改变单个Go程序间接拥有P的最大数量。
0~256(目前Go还不能保证P在大于256情况下程序会正常保持高效)
P的数量就是可运行G的队列数量。
这是P的限制修改,不会影响MG
程序运行时,默认的,P的最大值会被设置为和当前CPU总核数一致
Go语言程序的初始化和执行总是从main.main函数开始的。但是如果main包导入了其它的包,则会按照顺序将它们包含进main包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的init函数,如果一个包有多个init函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个init则是以出现的顺序依次调用(init不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当main包的所有包级常量、变量被创建和初始化完成,并且init函数被执行后,才会进入main.main函数,程序开始正常执行。
channel是goroutine之间通信和同步的重要组件。
go语言 不要通过共享内存来通信,而是通过通信来共享内存。
channel通过make创建:
创建无缓冲通道:make(chan datatype)
创建有缓冲通道:make(chan datatype,n)
go内置函数len和cap获取channel的元素数和容量。
无缓存通道的 len和cap都是0。
有缓存通道的len是没被读取的元素数,cap是整个通道的容量。
无缓冲通道可用于通信和同步,
有缓冲通道主要用于通信。
缓冲通道和消息队列类似,有削峰和增大吞吐量的功能。
<-chan T
chan<- T
chan T
len元素个数
cap容量
接收操作符 元素/通道 <- 元素/通道
1.通道类型是引用类型,被初始化之前值是nil
2.通道类型的变量是传递值的,不是保存值的,所以没有值表示法。
它的值具有即时性,无法用字面量准确表达。
3.发送
发送操作会使通道复制被发送的元素
4.取值
<-myChan
elem:=<-myChan
elem,ok:=<-myChan
5.close()
重复关闭会panic
可以读取,不可以写入
elem,ok:=<-myChan
当接收操作因通道关闭而结束时,ok为false,表示操作失败,否则为true
读取未初始化的通道值,会永久阻塞
6.range读取channel数据
for x:=range <-myChan{
}
7.select
for{
select{
case e1:=<-myChan:
...
case <-time.NewTimer(time.Second*1).C:
超时操作
default:
所有分支不满足会进入该分支
}
}
8.Ticker 定时读取
for _=range time.NewTicker(time.Second).C{
select{
case xxx
}
}
channel引起panic
goroutine之间协作主要涉及 通信、同步、通知、退出四个方面
1,通信:channel。
2.同步:
1.无缓存的channel;
2.sync.WaitGroup。
3.通知:
1.增加一个channel用于异常通知数据,然后通过select收敛进行处理。
2.context标准库
4.退出:
1.借助channel和select的广播机制(close channel 实现 broadcast) 实现退出
2.context标准库
编程常遇到的扇入扇出概念:
扇入:
多路通道聚合到一个通道处理,
go语言中,可以使用select聚合多条通道服务。
扇出:
将一个通道发散到多条通道处理,
go语言中,使用go关键字启动多个goroutine并发处理。
实现原理:
增加一个channel done 用于异常通知数据,然后通过select收敛进行处理;
借助channel和select的广播机制(close channel 实现 broadcast) 实现退出。
package main
import (
"math/rand"
"runtime"
)
func main() {
done := make(chan struct{
})
ch := generate(done)
//生产者就绪,goroutine有两个:main+生产者
println("goroutine num1:", runtime.NumGoroutine())
//消费
for i := 0; i < 2; i++ {
println(<-ch)
}
close(done)//通知退出
//已经停止生成,还可以读取通道中剩余的1个
for res := range ch {
println("res:", res)
}
//生成已退出,当前goroutine只剩main一个
println("goroutine num2:", runtime.NumGoroutine())
}
//生成随机数的函数
func generate(done chan struct{
}) chan int {
ch := make(chan int)
//生产者
go func() {
lb: // 跳出for循环
for {
select {
case ch <- rand.Int()://生成随机数
println("generate ok")
case <-done://接到通知,退出循环
println("done ok")
break lb
}
}
close(ch)//停止生产
}()
return ch
}
//结果
PS E:\mypro\company\mygrpc\gopro> go run .\main.go
goroutine num1: 2
5577006791947779410
generate ok
8674665223082153551
generate ok
res: 6129484611666145821
generate ok
package main
import (
"math/rand"
"runtime"
)
// 融合并发、缓冲、退出通知的生产者
func main() {
done := make(chan struct{
})
ch := generateInt(done)
for i := 0; i < 10; i++ {
println(<-ch)
}
println("goroutine num1:", runtime.NumGoroutine())
close(done)
for res := range ch {
println("res:", res)
}
println("goroutine num2:", runtime.NumGoroutine())
}
// 扇入
func generateInt(done chan struct{
}) chan int {
ch := make(chan int)
childDone := make(chan struct{
})
go func() {
//扇入两个生产者,可以相同或者不同
chA := <-generate(childDone)
chB := <-generate(childDone)
lb:
// 如何跳出for循环
for {
select {
//随机选择生产者数据
case ch <- chA:
println("generate 1")
case ch <- chB:
println("generate 2")
case <-done://接到退出通知,通知生产者也退出,然后退出循环
close(childDone)//通知生产者
break lb
}
}
close(ch)
}()
return ch
}
func generate(done chan struct{
}) chan int {
ch := make(chan int)
go func() {
println("lb1")
lb:
// 如何跳出for循环
for {
select {
case ch <- rand.Int():
case <-done:
println("okkkk")
break lb
}
}
println("return ")
close(ch)
}()
return ch
}
//结果
PS E:\mypro\company\mygrpc\gopro> go run .\main.go
lb1
lb1
generate 2
6129484611666145821
5577006791947779410
generate 1
generate 2
6129484611666145821
5577006791947779410
generate 1
generate 2
6129484611666145821
5577006791947779410
generate 1
generate 2
6129484611666145821
5577006791947779410
generate 1
generate 2
6129484611666145821
6129484611666145821
goroutine num1: 4
generate 2
generate 1
okkkk
return
okkkk
return
res: 5577006791947779410
goroutine num2: 1
package main
// 管道
/*
通道分为两个方向:读、写
假如函数的输入输出是相同的chan类型,则函数可以调用相同类型的函数生成调用链
*/
func main() {
in := generate() //0~9
out := chain(chain(in)) //2~11
for res := range out {
println("res:", res)
}
}
// 生产者
func generate() chan int {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
return ch
}
// 函数将channel内的数据统一做处理
func chain(in chan int) chan int {
out := make(chan int)
go func() {
for res := range in {
out <- res + 1
}
close(out)
}()
return out
}
//结果
PS E:\mypro\company\mygrpc\gopro> go run .\main.go
res: 2
res: 3
res: 4
res: 5
res: 6
res: 7
res: 8
res: 9
res: 10
res: 11
即,来一个任务起一个goroutine去处理
net/http包 server.go
func Serve(l net.Listener, handler Handler) error {
srv := &Server{
Handler: handler}
return srv.Serve(l)
}
func (srv *Server) Serve(l net.Listener) error {
...
for {
rw, e := l.Accept()
if cc := srv.ConnContext; cc != nil {
ctx = cc(ctx, rw)
if ctx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
//启动一个独立的goroutine处理web请求
go c.serve(ctx)
}
}
服务器编程使用最多的,
就是通过线程池来提高服务的并发处理能力,
go语言中,
可以使用固定数量的goroutine作为工作池。
package main
import "time"
/*
一个流程需要调用多个子调用,这些子调用之间没有依赖,
此时使用future模式可以减少串行耗费的时间:
1.使用channel作为函数参数
2.启动goroutine调用函数
3.通过channel传入参数
4.做其他可以并行的操作
5.channel异步获取结果
future最大的好处是将函数的通过调用转换成异步调用,
适用于任务需要多个子调用且子调用之间相互独立的场景。
一个例子,体验下思想
*/
type query struct {
sql chan string
result chan string
}
func execQuery(q query) {
go func() {
sql := <-q.sql
// ...
q.result <- "result from " + sql
}()
}
func main() {
q := query{
sql: make(chan string, 1),
result: make(chan string, 1),
}
go execQuery(q)
q.sql <- "select * from table"
// 做其他事情
time.Sleep(time.Second * 1)
// 获取结果
println("result:", <-q.result)
}
//结果
PS E:\mypro\company\mygrpc\gopro> go run .\main.go
result: result from select * from table
context包提供的功能:
1.退出通知机制
2.元数据传递
可以传递一些日志信息、调试信息或不影响主逻辑的可选数据。
context在grpc中的使用
Go语言核心编程