滴滴网站建设流程,seo快排优化,四川省铁路建设有限公司网站,做网站的入门书籍1. runtime包
1.1.1. runtime.Gosched()
让出CPU时间片#xff0c;重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤#xff0c;但是你妈让你去相亲,两种情况第一就是你相亲速度非常快#xff0c;见面就黄不耽误你继续烧烤#xff0c;第二种情况就是你相亲速度…1. runtime包
1.1.1. runtime.Gosched()
让出CPU时间片重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤但是你妈让你去相亲,两种情况第一就是你相亲速度非常快见面就黄不耽误你继续烧烤第二种情况就是你相亲速度特别慢见面就是你侬我侬的耽误了烧烤但是还馋就是耽误了烧烤你还得去烧烤)
package mainimport (fmtruntime
)func main() {go func(s string) {for i : 0; i 2; i {fmt.Println(s)}}(world)// 主协程for i : 0; i 2; i {// 切一下再次分配任务runtime.Gosched()fmt.Println(hello)}
}1.1.2. runtime.Goexit()
退出当前协程(一边烧烤一边相亲突然发现相亲对象太丑影响烧烤果断让她滚蛋然后也就没有然后了)
package mainimport (fmtruntime
)func main() {go func() {defer fmt.Println(A.defer)func() {defer fmt.Println(B.defer)// 结束协程runtime.Goexit()defer fmt.Println(C.defer)fmt.Println(B)}()fmt.Println(A)}()for {}
}1.1.3. runtime.GOMAXPROCS
Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上调度器会把Go代码同时调度到8个OS线程上GOMAXPROCS是m:n调度中的n。
Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
Go1.5版本之前默认使用的是单核心执行。Go1.5版本之后默认使用全部的CPU逻辑核心数。
我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果这里举个例子
func a() {for i : 1; i 10; i {fmt.Println(A:, i)}
}func b() {for i : 1; i 10; i {fmt.Println(B:, i)}
}func main() {runtime.GOMAXPROCS(1)go a()go b()time.Sleep(time.Second)
}两个任务只有一个逻辑核心此时是做完一个任务再做另一个任务。 将逻辑核心数设为2此时两个任务并行执行代码如下。
func a() {for i : 1; i 10; i {fmt.Println(A:, i)}
}func b() {for i : 1; i 10; i {fmt.Println(B:, i)}
}func main() {runtime.GOMAXPROCS(2)go a()go b()time.Sleep(time.Second)
}Go语言中的操作系统线程和goroutine的关系
1.一个操作系统线程对应用户态多个goroutine。2.go程序可以同时使用多个操作系统线程。3.goroutine和OS线程是多对多的关系即m:n。
2. Channel
2.1. channel
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性必须使用互斥量对内存进行加锁这种做法势必造成性能问题。
Go语言的并发模型是CSPCommunicating Sequential Processes提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
Go 语言中的通道channel是一种特殊的类型。通道像一个传送带或者队列总是遵循先入先出First In First Out的规则保证收发数据的顺序。每一个通道都是一个具体类型的导管也就是声明channel的时候需要为其指定元素类型。
2.2. channel类型
channel是一种类型一种引用类型。声明通道类型的格式如下 var 变量 chan 元素类型举几个例子 var ch1 chan int // 声明一个传递整型的通道var ch2 chan bool // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道2.3. 创建channel
通道是引用类型通道类型的空值是nil。
var ch chan int
fmt.Println(ch) // nil声明的通道后需要使用make函数初始化之后才能使用。
创建channel的格式如下 make(chan 元素类型, [缓冲大小])channel的缓冲大小是可选的。
举几个例子
ch4 : make(chan int)
ch5 : make(chan bool)
ch6 : make(chan []int)2.4. channel操作
通道有发送send、接收(receive和关闭close三种操作。
发送和接收都使用-符号。
现在我们先使用以下语句定义一个通道
ch : make(chan int)发送
将一个值发送到通道中。
ch - 10 // 把10发送到ch中接收
从一个通道中接收值。
x : - ch // 从ch中接收值并赋值给变量x
-ch // 从ch中接收值忽略结果关闭
我们通过调用内置的close函数来关闭通道。 close(ch)关于关闭通道需要注意的事情是只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的它和关闭文件是不一样的在结束操作之后关闭文件是必须要做的但关闭通道不是必须的。
关闭后的通道有以下特点 1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。2.5. 无缓冲的通道 无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码
func main() {ch : make(chan int)ch - 10fmt.Println(发送成功)
}上面这段代码能够通过编译但是执行的时候会出现以下错误 fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main().../src/github.com/pprof/studygo/day06/channel02/main.go:8 0x54为什么会出现deadlock错误呢
因为我们使用ch : make(chan int)创建的是无缓冲的通道无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点快递员给你打电话必须要把这个物品送到你的手中简单来说就是无缓冲的通道必须有接收才能发送。
上面的代码会阻塞在ch - 10这一行代码形成死锁那如何解决这个问题呢
一种方法是启用一个goroutine去接收值例如
func recv(c chan int) {ret : -cfmt.Println(接收成功, ret)
}
func main() {ch : make(chan int)go recv(ch) // 启用goroutine从通道接收值ch - 10fmt.Println(发送成功)
}无缓冲通道上的发送操作会阻塞直到另一个goroutine在该通道上执行接收操作这时值才能发送成功两个goroutine将继续执行。相反如果接收操作先执行接收方的goroutine将阻塞直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此无缓冲通道也被称为同步通道。
2.6. 有缓冲的通道
解决上面问题的方法还有一种就是使用有缓冲区的通道。 我们可以在使用make函数初始化通道的时候为其指定通道的容量例如
func main() {ch : make(chan int, 1) // 创建一个容量为1的有缓冲区通道ch - 10fmt.Println(发送成功)
}只要通道的容量大于零那么该通道就是有缓冲的通道通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子格子满了就装不下了就阻塞了等到别人取走一个快递员就能往里面放一个。
我们可以使用内置的len函数获取通道内元素的数量使用cap函数获取通道的容量虽然我们很少会这么做。
2.7. close()
可以通过内置的close()函数关闭channel如果你的管道不往里存值或者取值的时候一定记得关闭管道
package mainimport fmtfunc main() {c : make(chan int)go func() {for i : 0; i 5; i {c - i}close(c)}()for {if data, ok : -c; ok {fmt.Println(data)} else {break}}fmt.Println(main结束)
}2.8. 如何优雅的从通道循环取值
当通过通道发送有限的数据时我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。当通道被关闭时往该通道发送值会引发panic从该通道里接收的值一直都是类型零值。那如何判断一个通道是否被关闭了呢
我们来看下面这个例子
// channel 练习
func main() {ch1 : make(chan int)ch2 : make(chan int)// 开启goroutine将0~100的数发送到ch1中go func() {for i : 0; i 100; i {ch1 - i}close(ch1)}()// 开启goroutine从ch1中接收值并将该值的平方发送到ch2中go func() {for {i, ok : -ch1 // 通道关闭后再取值okfalseif !ok {break}ch2 - i * i}close(ch2)}()// 在主goroutine中从ch2中接收值打印for i : range ch2 { // 通道关闭后会退出for range循环fmt.Println(i)}
}从上面的例子中我们看到有两种方式在接收值的时候判断通道是否被关闭我们通常使用的是for range的方式。
2.9. 单向通道
有的时候我们会将通道作为参数在多个任务函数间传递很多时候我们在不同的任务函数中使用通道都会对其进行限制比如限制通道在函数中只能发送或只能接收。
Go语言中提供了单向通道来处理这种情况。例如我们把上面的例子改造如下
func counter(out chan- int) {for i : 0; i 100; i {out - i}close(out)
}func squarer(out chan- int, in -chan int) {for i : range in {out - i * i}close(out)
}
func printer(in -chan int) {for i : range in {fmt.Println(i)}
}func main() {ch1 : make(chan int)ch2 : make(chan int)go counter(ch1)go squarer(ch2, ch1)printer(ch2)
}其中 1.chan- int是一个只能发送的通道可以发送但是不能接收2.-chan int是一个只能接收的通道可以接收但是不能发送。在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的但反过来是不可以的。
2.10. 通道总结
channel常见的异常总结如下图 注意:关闭已经关闭的channel也会引发panic。
3. Goroutine池
3.1. worker poolgoroutine池
本质上是生产者消费者模型可以有效控制goroutine数量防止暴涨需求 计算一个数字的各个位数之和例如数字123结果为1236随机生成数字进行计算控制台输出结果如下 package mainimport (fmtmath/rand
)type Job struct {// idId int// 需要计算的随机数RandNum int
}type Result struct {// 这里必须传对象实例job *Job// 求和sum int
}func main() {// 需要2个管道// 1.job管道jobChan : make(chan *Job, 128)// 2.结果管道resultChan : make(chan *Result, 128)// 3.创建工作池createPool(64, jobChan, resultChan)// 4.开个打印的协程go func(resultChan chan *Result) {// 遍历结果管道打印for result : range resultChan {fmt.Printf(job id:%v randnum:%v result:%d\n, result.job.Id,result.job.RandNum, result.sum)}}(resultChan)var id int// 循环创建job输入到管道for {id// 生成随机数r_num : rand.Int()job : Job{Id: id,RandNum: r_num,}jobChan - job}
}// 创建工作池
// 参数1开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {// 根据开协程个数去跑运行for i : 0; i num; i {go func(jobChan chan *Job, resultChan chan *Result) {// 执行运算// 遍历job管道所有数据进行相加for job : range jobChan {// 随机数接过来r_num : job.RandNum// 随机数每一位相加// 定义返回值var sum intfor r_num ! 0 {tmp : r_num % 10sum tmpr_num / 10}// 想要的结果是Resultr : Result{job: job,sum: sum,}//运算结果扔到管道resultChan - r}}(jobChan, resultChan)}
} 参考文章go协程之Channel - 范斯猫