网站设计作品案例,wordpress 添加媒体库,中国建设官方网站企业,wordpress插件密钥实现目录#xff1a; (1)Go开发指南-Hello World (2)Go开发指南-Gin与Web开发 (3)Go开发指南-Goroutine
Goroutine
在java中我们要实现并发编程的时候#xff0c;通常要自己维护一个线程池#xff0c;并且需要去包装任务、调度任务和维护上下文切换。这个过程需要消耗大量的精…目录 (1)Go开发指南-Hello World (2)Go开发指南-Gin与Web开发 (3)Go开发指南-Goroutine
Goroutine
在java中我们要实现并发编程的时候通常要自己维护一个线程池并且需要去包装任务、调度任务和维护上下文切换。这个过程需要消耗大量的精力。
Go语言中有一种机制可以让系统自动把任务分配到CPU上实现并发执行而不需要人工去管理这些任务。这就是goroutine。
Goroutine类似于线程但比线程更轻量可以称之为协程。它由运行时runtime调度和管理自动进行上下文切换这也是go被称之为现代化编程语言的原因。
使用Goroutine
Go中使用goroutine非常简单只需要在调用函数的时候加上go关键字。一个goroutine必定对应一个函数可以创建多个goroutine去执行相同的函数。
下面是一个示例 func hello() {fmt.Println(Hello Goroutine!)
}
func main() {hello()fmt.Println(main goroutine done!)
}这个例子中hello函数和主函数中的打印信息是串行的。
先将hello函数改成goroutine的
func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println(main goroutine done!)
}再次执行会发现只打印main goroutine done!。这是因为main函数本身是在一个默认的goroutine中执行的当main函数结束时此goroutine运行结束在main函数中启动的其他goroutine也会随之退出。
修改main函数
unc main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println(main goroutine done!)time.Sleep(time.Second)
}此时再次执行就会再次打印两条信息了。
启动多个goroutine
在go中可以同时启动多个goroutine
package mainimport (fmtsync
)var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println(Hello Goroutine!, i)
}
func main() {for i : 0; i 10; i {wg.Add(1) // 启动一个goroutine就登记1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束
}
这里使用sync.WaitGroup来实现goroutine的同步。
执行代码会发现10个协程并发打印信息并且顺序是随机的goroutine调度是随机的。
Goroutine与线程
一个goroutine的栈内存在生命周期开始时只有2KB但可以按需增大和缩小最大可达到1GB。
GPM是go语言运行时runtime层面的实现是go语言自己实现的一套调度系统区别于操作系统调度线程。
事实上GPM并不是官方术语而是开发者用来概括go的并发模型的三大核心组件的Goroutine、Processor、Machine。
Goroutine拥有自己的栈和上下文其切换由运行时调度器管理不依赖于操作系统的线程管理因此比传统线程更轻量。
Processor表示逻辑处理器管理着goroutine的队列并负责调度goroutine到可用的machine上执行。P的数量决定了可以同时运行多个goroutine可通过runtime.GOMAXPROCS设置最大256默认与CPU核数相等。
Machine表示内核线程或系统线程是在操作系统层面上执行任务的线程。Go运行时会将goroutine绑定到M上运行。换句话说M负责实际执行P中的goroutine。当M在运行goroutine时可以根据情况继续运行该goroutine也可以将其切换出去以运行其他goroutine。
GMP示意图 从线程调度讲Go语言相比其他语言的优势在于goroutine是由go运行时自己调度的。这个调度器使用一个被称为m:n调度的技术即将m个goroutine调度到n个OS线程上。其一大特点是goroutine的调度是在用户态下完成的不涉及内核态与用户态之间频繁切换包括内存的分配与释放成本比调度OS线程低很多。
channel
很多场景下并发地协程之间是需要互相通信的比如经典的并发同步问题用两个协程交替打印奇数和偶数这时候就要在两个协程之间互相通信来保证打印的顺序。 Go通过channel实现协程间的通信。 共享内存也可以进行数据交换但是共享内存容易出现并发安全问题为了保证数据的准确性需要使用互斥量对内存进行加锁造成额外的性能消耗。 Channel 是有类型的管道遵循先进先出的规则保证数据的顺序。Channel 采用关键字chan 加上类型做声明赋值取值采用符号-。
Channel是引用类型默认为nil。
var ch chan int
fmt.Println(ch) // 输出为nil声明的通道需要使用使用make函数初始化之后才能使用
ch : make(chan int)channel操作
channel有发送接收和关闭三种操作。如下所示
func test(ch chan- int) {ch - 10close(ch)
}func main() {ch : make(chan int)go test(ch)fmt.Println(-ch)
}channel是有方向的chan 是一个双向通道既可以发送数据也可以从中接收数据。chan- 是一个单向通道只能往其中发送数据。-chan表示这是一个单向通道只能往外取数据。
关闭通道并不是必须的而是可以让系统自动垃圾回收。需要关闭通道的情况明确知道没有更多的数据会被发送到通道时可以关闭通道。关闭通道可以让接收方在读取所有数据后通过检测到通道的关闭信号安全地停止接收数据。
关闭后的通道有以下特点
对一个关闭的通道发送数据会导致panic。对一个关闭的通道接收数据会正常获取如果通道里没有值了会获取到对应类型的零值。重复关闭通道会导致panic。
一般只有发送方才会主动关闭通道。
无缓冲channel和缓冲channel
无缓冲channel
无缓冲channel又称为阻塞channel如下所示
func main() {ch : make(chan int)ch - 10fmt.Println(send success)
}这段代码可以编译通过但是执行会报错all goroutines are asleep - deadlock!。原因是这是一个无缓冲channel只有数据发送方但是没有接收方代码会在ch - 10 阻塞住形成死锁。
添加一个接收方解决死锁问题
func recv(ch chan int) {ret : -chfmt.Println(recv success, ret)
}func main() {ch : make(chan int)go recv(ch)ch - 10fmt.Println(send success)
}无缓冲通道上的发送操作会阻塞直到另一个goroutine在该通道上执行接收操作这时才能发送成功两个goroutine将继续执行。相反如果接收操作先执行接收方的goroutine将阻塞直到另一个goroutine在该通道上发送一个值。这与阻塞队列的工作方式是类似的。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此无缓冲通道也被称为同步通道。
有缓冲channel
下面创建一个有缓冲的channel
func main() {ch : make(chan int, 1)ch - 10fmt.Println(send success)
}只要channel的容量大于零则就是一个有缓冲的通道。
遍历通道
func main() {ch1 : make(chan int)ch2 : make(chan int)go func() {for i : 0; i 100; i {ch1 - i}close(ch1)}() // 匿名函数go func() {for {i, ok : -ch1 // if ok false, it means the channel is closedif !ok {break}ch2 - i * i}close(ch2)}() // 匿名函数for i : range ch2 { // the for struct will exits when channel is closedfmt.Println(i)}
}select
select是Go中的关键字可以同时响应多个channel的操作。其使用类似于switch语句有一系列case 分支和一个默认的分支。每个case会对应一个通道的通信过程。select会一直等待直到某个case的通信操作完成时就会执行case分支对应的语句。如下所示
func test1(ch chan string) {time.Sleep(time.Second * 5)ch - test1
}func test2(ch chan string) {time.Sleep(time.Second * 2)ch - test2
}func main() {output1 : make(chan string)output2 : make(chan string)go test1(output1)go test2(output2)select {case s1 : -output1:fmt.Println(s1, s1)case s2 : -output2:fmt.Println(s2, s2)}
}在这个例子中只要任何一个通道的通信完成就会执行对应的case分支。如果多个channel同时ready会随机选择一个执行。
参考资料
[1]. https://go.dev/doc/tutorial/ [2].https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/channel.html