简介
使用goroutine和channel实现gin的并发前,先认识下goroutine和channel,特别的简单,以下几个示例就可以完全掌握这个轻量级内置功能的应用
Goroutine
通过go关键字执行goroutine, 在main函数中创建了一个新的 goroutine 来执行timesThree函数并继续执行下一条指令。因此,fmt.Println(“Done!”)在 goroutine 之前执行
package main
import (
"fmt"
"time"
)
/*
* Goroutine和channels实现并发
*
*
*
*/
/// @dev 定义一个mul时间的计数器
func timesThree(number int) {
fmt.Println(number * 3)
}
func main() {
fmt.Println("We are executing a goroutine")
g
fmt.Println("Done !")
time.Sleep(time.Second)
}
分别输出:
We are executing a goroutine
Done!
9
Process finished with the exit code 0
Channel
通过初始化channel可以实现把参数丢进channel里进行传输,通过<-取值和存值
*注:channel需要不断的接收和取值,类似于队列,需要make关键字创建一个内存
package main
import (
"fmt"
)
/*
* Goroutine和channels实现并发
*
*
*
*/
/// @dev 定义一个mul时间的计数器
func timesThree(number int, ch chan int) {
result := number * 3
fmt.Println(result)
// 把结果丢到channel里
ch <- result
}
func main() {
fmt.Println("We are executing a goroutine")
//channel创建时要新建内存,记得加make
ch := make(chan int)
go timesThree(3, ch)
//从channel中取回计算的值
result := <-ch
fmt.Printf("The result is: %v", result)
}
分别输出:
We are executing a goroutine
9
The result is: 9
Process finished with the exit code 0
缓冲Channel
有时候goroutine执行完成后,需要返回多个值,所以需要一个缓冲Channel去接收
package main
import (
"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创建时要新建内存,记得加make
ch := make(chan int)
go timesThree(arr, ch)
time.Sleep(time.Second)
//从channel中取回计算的值
result := <-ch
fmt.Printf("The result is: %v", result)
}
分别输出:
We are executing a goroutine
The result is: 6
Process finished with the exit code 0
package main
import (
"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创建时要新建内存,记得加make
ch := 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 main
import (
"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 goroutine
Result: %v
2
Result: %v
3
Result: %v
4
Goroutine之间的通道
通道不仅用于goroutine和main函数之间的交互,它们还提供了一种在不同goroutine之间进行通信的方式。例如,创建一个函数,将返回的每个结果减去3, timesThree的前提是它是偶数
即使在这种情况下,不必minusThree像 goroutine 一样运行并通过通道返回结果,它也说明了 goroutine 之间的交互是如何工作的。当您在一个解决方案中有两个不同的功能需要高性能并且其中一个的某些条件会影响另一个的结果时,这尤其有用。
package main
import (
"fmt"
)
/*
* Goroutine和channels实现并发
*
*
*
*/
func timesThree(arr []int, ch chan int) {
minusCh := make(chan int, 3)
for _, elem := range arr {
value := elem * 3
if value%2 == 0 {
go minusThree(value, minusCh)
value = <-minusCh
}
ch <- value
}
}
func minusThree(number int, ch chan int) {
ch <- number - 3
fmt.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 goroutine
The functions continues after returning the result
The functions continues after returning the result
Result: 3
Result: 9
Result: 9
Range范围和关闭Channel
使用这些特征可以从goroutine接收连续的元素,直到它关闭通道,使用该指令,for i := range ch 可以从goroutine的结果发送后立即对其进行迭代(可以理解为立刻迭代队列)。一旦完成发送数据接收,close函数可以关闭通道
注:如果使用 for i := range ch 循环通道,没有进行close(ch)关闭通道,程序会崩溃
输出:
*”fatal error: all goroutines are asleep - deadlock!”
package main
import (
"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 main
import (
"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 goroutine
Result minusThree: -1
Result timesThree: 6
Result minusThree: 0
Result timesThree: 9
Result timesThree: 12
Result timesThree: 15
Result minusThree: 1
Result timesThree: 18
Result minusThree: 2
Result minusThree: 3
Process finished with the exit code 0
sync.Mutex互斥锁
使用并发时可能出现的一个问题是,当两个gshare相同的资源不应该被多个 goroutine 同时访问时。在并发情况下,修改共享资源的代码块称为临界区。
因为 goroutine 会同时访问和重新分配相同的内存空间,所以我们会得到有问题的结果。在这种情况下,n *= 3将是临界区。
我们可以通过使用sync.Mutex. 这可以防止多个 goroutine 同时访问Lock()和Unlock()函数之间的指令。
package main
import (
"fmt"
"time"
)
var n = 1
func timesThree() {
n *= 3
fmt.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 goroutine
27
81
3
9
243
6561
729
19683
59049
2187
Process finished with the exit code 0
package main
import (
"fmt"
"time"
)
var n = 1
var mu sync.Mutex
func timesThree() {
mu.Lock()
defer mu.Unlock()
n *= 3
fmt.Println(n)
}
func main() {
fmt.Println("We are executing a goroutine")
for i := 0; i < 10; i++ {
go timesThree()
}
time.Sleep(time.Second)
}