Go语言之gorountine与管道初体验
Posted 程序彤
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言之gorountine与管道初体验相关的知识,希望对你有一定的参考价值。
Go之gorountine
一个go线程上,可以启动多个协程。协程是轻量级的线程。
特点:
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 是轻量级的线程
go的协程机制,可以轻松的开启上万个协程。其他编程语言的并发机制基于线程,开启过多的线程,它们的资源耗费大。
package main
import (
"fmt"
"strconv"
"time"
)
func main(){
go test() // main主线程和test协程同时执行
for i := 0; i < 5; i++ { // 主线程执行5次,协程执行6次就跟着退出
fmt.Println("main() hello main..."+strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func test(){
for i := 0; i < 10; i++ {
fmt.Println("test() hello test..."+strconv.Itoa(i))
time.Sleep(time.Second)
}
}
显示和设置cpu
package main
import (
"fmt"
"runtime"
)
func main() {
cpuNum := runtime.NumCPU()
runtime.GOMAXPROCS(cpuNum) // 默认可不设置
fmt.Println("cpu个数为:",cpuNum)
}
多个协程并发求阶乘之最佳解法-channel(队列)
- 法1:低水平,全局变量互斥锁解决。如下
package main
import (
"fmt"
"sync"
"time"
)
var(
myMap = make(map[int]int,10)
lock sync.Mutex // 全局变量互斥锁
)
func test(n int){
res := 1
for i := 1; i <= n; i++ {
res *= i
}
// 将res放入map中
lock.Lock()
myMap[n] = res
lock.Unlock()
}
func main() {
//cpuNum := runtime.NumCPU()
//
//runtime.GOMAXPROCS(cpuNum) // 默认可不设置
//fmt.Println("cpu个数为:",cpuNum)
// 开启多个协程
for i := 0; i < 20; i++ {
go test(i)
}
time.Sleep(time.Second*5)
lock.Lock()
// 遍历输出的结果
for i,v := range myMap{
fmt.Printf("map[%v]=%v\\n",i,v)
}
lock.Unlock()
}
- 主线程要专门等待几秒,这样显然不可以。
主线程再等待所有协程全部完成的时间 无法确定,以上代码设置5秒,这显然不好,仅仅只是估算。
新的通讯机制,管道channel - 队列
channel管道(队列)线程安全的
多个协程同时操作同一个管道时,不会发生资源竞争问题
channel是引用类型,必须初始化后才可写入数据,即make后才可使用
管道是有类型的,intChan只能写入整数int
管道不可自动增长,而map可以自动扩容。
管道的初始化,读、取数据
存满后,不可再存数据了。可边存边取,如流动的数据,流动的水。
package main
import "fmt"
func main(){
// 管道的初始化
var channel chan int
channel = make(chan int,3)
// 向管道存数据,之后即可读数据
channel <- 5 // first
num1 := 666
channel <- num1
fmt.Printf("管道的地址%v\\n",channel)
// 从管道取数据,FIFO
fmt.Println("前",len(channel),cap(channel))
var num2 int
num2 = <- channel
fmt.Println("后",len(channel),cap(channel))
fmt.Println(num2) // FIFO 5先进
}
类型断言的最佳实践
package main
import "fmt"
func main(){
var channel chan interface{}
channel = make(chan interface{},10)
cat1:=Cat{"小橘",3}
cat2:=Cat{"灰白",2}
channel <- cat1
channel <- cat2
channel <- 10
channel <- "lwt"
cat11 := <- channel
fmt.Printf("cat11的类型%T,值%v\\n",cat11,cat11)
// 但是这里却不能cat11.Name,??? 因为编译器认为这是空接口
// 类型断言的最佳实践
newCat := cat11.(Cat)
fmt.Println(newCat.Name)
}
type Cat struct {
Name string
Age int
}
管道的关闭和遍历(先关闭再遍历)
在遍历管道前,先关闭管道,防止发生死锁。
关闭
关闭后可读不可取。(类似加写锁)
close(管道名 )
遍历
必须使用for-range(前提先关闭管道),且无索引返回。
若不关闭将报错如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
package main
import "fmt"
func main(){
channel := make(chan int,2)
channel <- 10
channel <- 99
//close(channel)
// 管道关闭后,只可取,不可存
num1 := <- channel
//channel <- 999
fmt.Println(num1)
// 当管道关闭后,才可遍历,for-range无索引
for v := range channel{
fmt.Println(v)
}
}
只写管道
var chan1 chan<- int
chan1 = make(chan int,3)
只读管道
var chan2 <-chan int
chan2 = make(chan int,3)
使用场景:
通常默认声明,才封装的方法参数中传入引用类型管道,ch chan<- int或ch <-chan int
协程的异常处理
如果我们启动的协程出现异常错误panic,必须捕获这个panic,否则造成整个程序崩溃。这是我们使用recover()返回的err来输出预警。这样主线程和其他协程不受到影响
package main
import (
"fmt"
"time"
)
func main(){
go sayHello()
go makeError()
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
fmt.Println("main()防止主线程跑完")
}
}
func sayHello(){
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
fmt.Println(i)
}
}
func makeError(){
defer func() {
// 捕获test抛出的panic
if err:=recover();err!=nil {
fmt.Println("makeError()发生错误,发生预警,err=",err)
}
}()
var map1 map[int]string
//map1 = make(map[int]string,3)
map1[0] = "111"
}
以上是关于Go语言之gorountine与管道初体验的主要内容,如果未能解决你的问题,请参考以下文章
我的Go+语言初体验——Go语言总结篇(语法+实际问题解决方案)