简介
使用goroutine和channel实现gin的并发前,先认识下goroutine和channel,特别的简单,以下几个示例就可以完全掌握这个轻量级内置功能的应用
Goroutine
通过go关键字执行goroutine, 在main函数中创建了一个新的 goroutine 来执行timesThree函数并继续执行下一条指令。因此,fmt.Println(“Done!”)在 goroutine 之前执行
package mainimport ("fmt""time")/** Goroutine和channels实现并发****//// @dev 定义一个mul时间的计数器func timesThree(number int) {fmt.Println(number * 3)}func main() {fmt.Println("We are executing a goroutine")gfmt.Println("Done !")time.Sleep(time.Second)}分别输出:We are executing a goroutineDone!9Process finished with the exit code 0
Channel
通过初始化channel可以实现把参数丢进channel里进行传输,通过<-取值和存值
*注:channel需要不断的接收和取值,类似于队列,需要make关键字创建一个内存
package mainimport ("fmt")/** Goroutine和channels实现并发****//// @dev 定义一个mul时间的计数器func timesThree(number int, ch chan int) {result := number * 3fmt.Println(result)// 把结果丢到channel里ch <- result}func main() {fmt.Println("We are executing a goroutine")//channel创建时要新建内存,记得加makech := make(chan int)go timesThree(3, ch)//从channel中取回计算的值result := <-chfmt.Printf("The result is: %v", result)}分别输出:We are executing a goroutine9The result is: 9Process finished with the exit code 0
缓冲Channel
有时候goroutine执行完成后,需要返回多个值,所以需要一个缓冲Channel去接收
package mainimport ("fmt""time")/** Goroutine和channels实现并发****//// @dev 定义一个mul时间的计数器func timesThree(arr []int, ch chan int) {for _, elem := range arr {ch <- elem * 3}}func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4}//channel创建时要新建内存,记得加makech := make(chan int)go timesThree(arr, ch)time.Sleep(time.Second)//从channel中取回计算的值result := <-chfmt.Printf("The result is: %v", result)}分别输出:We are executing a goroutineThe result is: 6Process finished with the exit code 0package mainimport ("fmt")/** Goroutine和channels实现并发****//// @dev 定义一个mul时间的计数器func timesThree(arr []int, ch chan int) {for _, elem := range arr {ch <- elem * 3}}func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4}//channel创建时要新建内存,记得加makech := make(chan int)go timesThree(arr, ch)//从channel中取回计算的值//通过循环长度,获取通道所有的值for i := 0; i < len(arr); i++ {fmt.Printf("The result is: %v", <-ch)}}
匿名函数作为Goroutine
另一个很棒的特性是可以将匿名函数作为 goroutine 执行,如果我们不重用它的话。请注意,我们在关键字之后声明函数,go并在最后的大括号之后的括号之间传递参数。
package mainimport ("fmt")/** Goroutine和channels实现并发****/func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4}//创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别ch := make(chan int, len(arr))go func(arr []int, ch chan int) {for _, elem := range arr {ch <- elem}}(arr, ch)for i := 0; i < len(arr); i++ {fmt.Println("Result: %v \n", <-ch)}}分别输出:We are executing a goroutineResult: %v2Result: %v3Result: %v4
Goroutine之间的通道
通道不仅用于goroutine和main函数之间的交互,它们还提供了一种在不同goroutine之间进行通信的方式。例如,创建一个函数,将返回的每个结果减去3, timesThree的前提是它是偶数
即使在这种情况下,不必minusThree像 goroutine 一样运行并通过通道返回结果,它也说明了 goroutine 之间的交互是如何工作的。当您在一个解决方案中有两个不同的功能需要高性能并且其中一个的某些条件会影响另一个的结果时,这尤其有用。
package mainimport ("fmt")/** Goroutine和channels实现并发****/func timesThree(arr []int, ch chan int) {minusCh := make(chan int, 3)for _, elem := range arr {value := elem * 3if value%2 == 0 {go minusThree(value, minusCh)value = <-minusCh}ch <- value}}func minusThree(number int, ch chan int) {ch <- number - 3fmt.Println("The functions continues after returning the result")}func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4}//创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别ch := make(chan int, len(arr))go timesThree(arr, ch)for i := 0; i < len(arr); i++ {fmt.Printf("Result: %v \n", <-ch)}}分别输出:We are executing a goroutineThe functions continues after returning the resultThe functions continues after returning the resultResult: 3Result: 9Result: 9
Range范围和关闭Channel
使用这些特征可以从goroutine接收连续的元素,直到它关闭通道,使用该指令,for i := range ch 可以从goroutine的结果发送后立即对其进行迭代(可以理解为立刻迭代队列)。一旦完成发送数据接收,close函数可以关闭通道
注:如果使用 for i := range ch 循环通道,没有进行close(ch)关闭通道,程序会崩溃
输出:
*”fatal error: all goroutines are asleep - deadlock!”
package mainimport ("fmt")/** Goroutine和channels实现并发****/func timesThree(arr []int, ch chan int) {//调用defer延迟函数,完成接收后关闭通道defer close(ch)for _, elem := range arr {ch <- elem}}func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4}//创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别ch := make(chan int, len(arr))go timesThree(arr, ch)for i := range ch {fmt.Printf("Result: %v \n", i)}}
select 通道分流控制
我们如何同时从多个通道读取数据?作为一种同时等待多个通道的方式,防止一个通道阻塞另一个通道。
package mainimport ("fmt")/** Goroutine和channels实现并发****/func timesThree(arr []int, ch chan int) {for _, elem := range arr {ch <- elem * 3}}func minusThree(arr []int, ch chan int) {for _, elem := range arr {ch <- elem - 3}}func main() {fmt.Println("We are executing a goroutine")arr := []int{2, 3, 4, 5, 6}ch := make(chan int, len(arr))minusCh := make(chan int, len(arr))go timesThree(arr, ch)go minusThree(arr, minusCh)for i := 0; i < len(arr)*2; i++ {select {case msg1 := <-ch:fmt.Printf("Result timesThree: %v \n", msg1)case msg2 := <-minusCh:fmt.Printf("Result minusThree: %v \n", msg2)default:fmt.Println("Non blocking way of listening to multiple channels")}}}分别输出:We are executing a goroutineResult minusThree: -1Result timesThree: 6Result minusThree: 0Result timesThree: 9Result timesThree: 12Result timesThree: 15Result minusThree: 1Result timesThree: 18Result minusThree: 2Result minusThree: 3Process finished with the exit code 0
sync.Mutex互斥锁
使用并发时可能出现的一个问题是,当两个gshare相同的资源不应该被多个 goroutine 同时访问时。在并发情况下,修改共享资源的代码块称为临界区。
因为 goroutine 会同时访问和重新分配相同的内存空间,所以我们会得到有问题的结果。在这种情况下,n *= 3将是临界区。
我们可以通过使用sync.Mutex. 这可以防止多个 goroutine 同时访问Lock()和Unlock()函数之间的指令。
package mainimport ("fmt""time")var n = 1func timesThree() {n *= 3fmt.Println(n)}func main() {fmt.Println("We are executing a goroutine")for i := 0; i < 10; i++ {go timesThree()}time.Sleep(time.Second)}错误的结果分别输出:We are executing a goroutine278139243656172919683590492187Process finished with the exit code 0
package mainimport ("fmt""time")var n = 1var mu sync.Mutexfunc timesThree() {mu.Lock()defer mu.Unlock()n *= 3fmt.Println(n)}func main() {fmt.Println("We are executing a goroutine")for i := 0; i < 10; i++ {go timesThree()}time.Sleep(time.Second)}
