简介

使用goroutine和channel实现gin的并发前,先认识下goroutine和channel,特别的简单,以下几个示例就可以完全掌握这个轻量级内置功能的应用

Goroutine

通过go关键字执行goroutine, 在main函数中创建了一个新的 goroutine 来执行timesThree函数并继续执行下一条指令。因此,fmt.Println(“Done!”)在 goroutine 之前执行

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. /*
  7. * Goroutine和channels实现并发
  8. *
  9. *
  10. *
  11. */
  12. /// @dev 定义一个mul时间的计数器
  13. func timesThree(number int) {
  14. fmt.Println(number * 3)
  15. }
  16. func main() {
  17. fmt.Println("We are executing a goroutine")
  18. g
  19. fmt.Println("Done !")
  20. time.Sleep(time.Second)
  21. }
  22. 分别输出:
  23. We are executing a goroutine
  24. Done!
  25. 9
  26. Process finished with the exit code 0

Channel

通过初始化channel可以实现把参数丢进channel里进行传输,通过<-取值和存值
*注:channel需要不断的接收和取值,类似于队列,需要make关键字创建一个内存

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. /*
  6. * Goroutine和channels实现并发
  7. *
  8. *
  9. *
  10. */
  11. /// @dev 定义一个mul时间的计数器
  12. func timesThree(number int, ch chan int) {
  13. result := number * 3
  14. fmt.Println(result)
  15. // 把结果丢到channel里
  16. ch <- result
  17. }
  18. func main() {
  19. fmt.Println("We are executing a goroutine")
  20. //channel创建时要新建内存,记得加make
  21. ch := make(chan int)
  22. go timesThree(3, ch)
  23. //从channel中取回计算的值
  24. result := <-ch
  25. fmt.Printf("The result is: %v", result)
  26. }
  27. 分别输出:
  28. We are executing a goroutine
  29. 9
  30. The result is: 9
  31. Process finished with the exit code 0

缓冲Channel

有时候goroutine执行完成后,需要返回多个值,所以需要一个缓冲Channel去接收

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. /*
  7. * Goroutine和channels实现并发
  8. *
  9. *
  10. *
  11. */
  12. /// @dev 定义一个mul时间的计数器
  13. func timesThree(arr []int, ch chan int) {
  14. for _, elem := range arr {
  15. ch <- elem * 3
  16. }
  17. }
  18. func main() {
  19. fmt.Println("We are executing a goroutine")
  20. arr := []int{2, 3, 4}
  21. //channel创建时要新建内存,记得加make
  22. ch := make(chan int)
  23. go timesThree(arr, ch)
  24. time.Sleep(time.Second)
  25. //从channel中取回计算的值
  26. result := <-ch
  27. fmt.Printf("The result is: %v", result)
  28. }
  29. 分别输出:
  30. We are executing a goroutine
  31. The result is: 6
  32. Process finished with the exit code 0
  33. package main
  34. import (
  35. "fmt"
  36. )
  37. /*
  38. * Goroutine和channels实现并发
  39. *
  40. *
  41. *
  42. */
  43. /// @dev 定义一个mul时间的计数器
  44. func timesThree(arr []int, ch chan int) {
  45. for _, elem := range arr {
  46. ch <- elem * 3
  47. }
  48. }
  49. func main() {
  50. fmt.Println("We are executing a goroutine")
  51. arr := []int{2, 3, 4}
  52. //channel创建时要新建内存,记得加make
  53. ch := make(chan int)
  54. go timesThree(arr, ch)
  55. //从channel中取回计算的值
  56. //通过循环长度,获取通道所有的值
  57. for i := 0; i < len(arr); i++ {
  58. fmt.Printf("The result is: %v", <-ch)
  59. }
  60. }

匿名函数作为Goroutine

另一个很棒的特性是可以将匿名函数作为 goroutine 执行,如果我们不重用它的话。请注意,我们在关键字之后声明函数,go并在最后的大括号之后的括号之间传递参数。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. /*
  6. * Goroutine和channels实现并发
  7. *
  8. *
  9. *
  10. */
  11. func main() {
  12. fmt.Println("We are executing a goroutine")
  13. arr := []int{2, 3, 4}
  14. //创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别
  15. ch := make(chan int, len(arr))
  16. go func(arr []int, ch chan int) {
  17. for _, elem := range arr {
  18. ch <- elem
  19. }
  20. }(arr, ch)
  21. for i := 0; i < len(arr); i++ {
  22. fmt.Println("Result: %v \n", <-ch)
  23. }
  24. }
  25. 分别输出:
  26. We are executing a goroutine
  27. Result: %v
  28. 2
  29. Result: %v
  30. 3
  31. Result: %v
  32. 4

Goroutine之间的通道

通道不仅用于goroutine和main函数之间的交互,它们还提供了一种在不同goroutine之间进行通信的方式。例如,创建一个函数,将返回的每个结果减去3, timesThree的前提是它是偶数

即使在这种情况下,不必minusThree像 goroutine 一样运行并通过通道返回结果,它也说明了 goroutine 之间的交互是如何工作的。当您在一个解决方案中有两个不同的功能需要高性能并且其中一个的某些条件会影响另一个的结果时,这尤其有用。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. /*
  6. * Goroutine和channels实现并发
  7. *
  8. *
  9. *
  10. */
  11. func timesThree(arr []int, ch chan int) {
  12. minusCh := make(chan int, 3)
  13. for _, elem := range arr {
  14. value := elem * 3
  15. if value%2 == 0 {
  16. go minusThree(value, minusCh)
  17. value = <-minusCh
  18. }
  19. ch <- value
  20. }
  21. }
  22. func minusThree(number int, ch chan int) {
  23. ch <- number - 3
  24. fmt.Println("The functions continues after returning the result")
  25. }
  26. func main() {
  27. fmt.Println("We are executing a goroutine")
  28. arr := []int{2, 3, 4}
  29. //创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别
  30. ch := make(chan int, len(arr))
  31. go timesThree(arr, ch)
  32. for i := 0; i < len(arr); i++ {
  33. fmt.Printf("Result: %v \n", <-ch)
  34. }
  35. }
  36. 分别输出:
  37. We are executing a goroutine
  38. The functions continues after returning the result
  39. The functions continues after returning the result
  40. Result: 3
  41. Result: 9
  42. Result: 9

Range范围和关闭Channel

使用这些特征可以从goroutine接收连续的元素,直到它关闭通道,使用该指令,for i := range ch 可以从goroutine的结果发送后立即对其进行迭代(可以理解为立刻迭代队列)。一旦完成发送数据接收,close函数可以关闭通道
注:如果使用 for i := range ch 循环通道,没有进行close(ch)关闭通道,程序会崩溃
输出:
*”fatal error: all goroutines are asleep - deadlock!”

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. /*
  6. * Goroutine和channels实现并发
  7. *
  8. *
  9. *
  10. */
  11. func timesThree(arr []int, ch chan int) {
  12. //调用defer延迟函数,完成接收后关闭通道
  13. defer close(ch)
  14. for _, elem := range arr {
  15. ch <- elem
  16. }
  17. }
  18. func main() {
  19. fmt.Println("We are executing a goroutine")
  20. arr := []int{2, 3, 4}
  21. //创建通道时指定通道的长度,有点像创建静态数组和动态数组的区别
  22. ch := make(chan int, len(arr))
  23. go timesThree(arr, ch)
  24. for i := range ch {
  25. fmt.Printf("Result: %v \n", i)
  26. }
  27. }

select 通道分流控制

我们如何同时从多个通道读取数据?作为一种同时等待多个通道的方式,防止一个通道阻塞另一个通道。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. /*
  6. * Goroutine和channels实现并发
  7. *
  8. *
  9. *
  10. */
  11. func timesThree(arr []int, ch chan int) {
  12. for _, elem := range arr {
  13. ch <- elem * 3
  14. }
  15. }
  16. func minusThree(arr []int, ch chan int) {
  17. for _, elem := range arr {
  18. ch <- elem - 3
  19. }
  20. }
  21. func main() {
  22. fmt.Println("We are executing a goroutine")
  23. arr := []int{2, 3, 4, 5, 6}
  24. ch := make(chan int, len(arr))
  25. minusCh := make(chan int, len(arr))
  26. go timesThree(arr, ch)
  27. go minusThree(arr, minusCh)
  28. for i := 0; i < len(arr)*2; i++ {
  29. select {
  30. case msg1 := <-ch:
  31. fmt.Printf("Result timesThree: %v \n", msg1)
  32. case msg2 := <-minusCh:
  33. fmt.Printf("Result minusThree: %v \n", msg2)
  34. default:
  35. fmt.Println("Non blocking way of listening to multiple channels")
  36. }
  37. }
  38. }
  39. 分别输出:
  40. We are executing a goroutine
  41. Result minusThree: -1
  42. Result timesThree: 6
  43. Result minusThree: 0
  44. Result timesThree: 9
  45. Result timesThree: 12
  46. Result timesThree: 15
  47. Result minusThree: 1
  48. Result timesThree: 18
  49. Result minusThree: 2
  50. Result minusThree: 3
  51. Process finished with the exit code 0

sync.Mutex互斥锁

使用并发时可能出现的一个问题是,当两个gshare相同的资源不应该被多个 goroutine 同时访问时。在并发情况下,修改共享资源的代码块称为临界区

因为 goroutine 会同时访问和重新分配相同的内存空间,所以我们会得到有问题的结果。在这种情况下,n *= 3将是临界区

我们可以通过使用sync.Mutex. 这可以防止多个 goroutine 同时访问Lock()和Unlock()函数之间的指令。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. var n = 1
  7. func timesThree() {
  8. n *= 3
  9. fmt.Println(n)
  10. }
  11. func main() {
  12. fmt.Println("We are executing a goroutine")
  13. for i := 0; i < 10; i++ {
  14. go timesThree()
  15. }
  16. time.Sleep(time.Second)
  17. }
  18. 错误的结果
  19. 分别输出:
  20. We are executing a goroutine
  21. 27
  22. 81
  23. 3
  24. 9
  25. 243
  26. 6561
  27. 729
  28. 19683
  29. 59049
  30. 2187
  31. Process finished with the exit code 0
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. var n = 1
  7. var mu sync.Mutex
  8. func timesThree() {
  9. mu.Lock()
  10. defer mu.Unlock()
  11. n *= 3
  12. fmt.Println(n)
  13. }
  14. func main() {
  15. fmt.Println("We are executing a goroutine")
  16. for i := 0; i < 10; i++ {
  17. go timesThree()
  18. }
  19. time.Sleep(time.Second)
  20. }