概述

Go协程(Goroutine)是与其他函数或方法同时运行的函数或方法。可以认为Go协程是轻量级的线程。与创建线程相比,创建Go协程的成本很小。因此在Go中同时运行上千个协程是很常见的。

Go协程对比线程的优点

与线程相比,Go协程的开销非常小。Go协程的堆栈大小只有几kb,它可以根据应用程序的需要而增长和缩小,而线程必须指定堆栈的大小,并且堆栈的大小是固定的。

Go协程被多路复用到较少的OS线程。在一个程序中数千个Go协程可能只运行在一个线程中。如果该线程中的任何一个Go协程阻塞(比如等待用户输入),那么Go会创建一个新的OS线程并将其余的Go协程移动到这个新的OS线程。所有这些操作都是 runtime 来完成的,而我们程序员不必关心这些复杂的细节,只需要利用 Go 提供的简洁的 API 来处理并发就可以了。

Go 协程之间通过信道(channel)进行通信。信道可以防止多个协程访问共享内存时发生竟险(race condition)。信道可以想象成多个协程之间通信的管道。
go 协程 - 图1
go 协程 - 图2

goroutine 可能切换的点

  • 非强占式
  • I/O ,select
  • channel
  • 等待锁
  • 调用函数
  • runtime.Gosched()

只是参考,不能保证切换
go 协程 - 图3

代码一

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func sample(message chan string) {
  7. message <- "hello goroutine!1"
  8. message <- "hello goroutine!2"
  9. message <- "hello goroutine!3"
  10. message <- "hello goroutine!4"
  11. }
  12. func sampleTwo(message chan string) {
  13. time.Sleep(2 * time.Second)
  14. str := <-message
  15. str = str + "I am goroutinetwo"
  16. message <- str
  17. close(message)
  18. }
  19. func main() {
  20. /**
  21. * 队列为3的
  22. */
  23. var message = make(chan string, 3)
  24. go sample(message)
  25. go sampleTwo(message)
  26. time.Sleep(3 * time.Second)
  27. str := <-message
  28. fmt.Println(str)
  29. fmt.Println(<-message)
  30. for strTemp := range message {
  31. fmt.Println(strTemp)
  32. }
  33. }

代码二

select 多个队列的随机选择

  1. package main
  2. import (
  3. "strconv"
  4. "time"
  5. "fmt"
  6. )
  7. func sample(ch chan string) {
  8. for i := 0; i < 19; i++ {
  9. ch <- "I am sample num :" + strconv.Itoa(i)
  10. time.Sleep(1 * time.Second)
  11. }
  12. }
  13. func sampleTwo(ch chan int) {
  14. for i := 0; i < 10; i++ {
  15. ch <- i
  16. time.Sleep(2 * time.Second)
  17. }
  18. }
  19. func main() {
  20. /**
  21. * 队列为3的
  22. */
  23. var ch1 = make(chan string, 3)
  24. var ch2 = make(chan int, 5)
  25. for i := 0; i < 10; i++ {
  26. go sample(ch1)
  27. go sampleTwo(ch2)
  28. }
  29. // select 多个队列的随机选择
  30. for i := 0; i < 1000; i++ {
  31. select {
  32. case str, ch1Check := <-ch1:
  33. if !ch1Check {
  34. fmt.Println("ch1Check false")
  35. }
  36. fmt.Println(str)
  37. case p, ch2Check := <-ch2:
  38. if !ch2Check {
  39. fmt.Println("ch2Check false")
  40. }
  41. fmt.Println(p)
  42. }
  43. }
  44. time.Sleep(60 * time.Second)
  45. }

加大两个sample的时间差

  1. package main
  2. import (
  3. "strconv"
  4. "time"
  5. "fmt"
  6. )
  7. func sample(ch chan string) {
  8. for i := 0; i < 19; i++ {
  9. ch <- "I am sample num :" + strconv.Itoa(i)
  10. time.Sleep(3 * time.Second)
  11. }
  12. }
  13. func sampleTwo(ch chan int) {
  14. for i := 0; i < 10; i++ {
  15. ch <- i
  16. time.Sleep(60 * time.Second)
  17. }
  18. }
  19. func main() {
  20. /**
  21. * 队列为3的
  22. */
  23. var ch1 = make(chan string, 3)
  24. var ch2 = make(chan int, 5)
  25. for i := 0; i < 10; i++ {
  26. go sample(ch1)
  27. go sampleTwo(ch2)
  28. }
  29. // select 多个队列的随机选择
  30. for {
  31. select {
  32. case str, ch1Check := <-ch1:
  33. if !ch1Check {
  34. fmt.Println("ch1Check false")
  35. }
  36. fmt.Println(str)
  37. case p, ch2Check := <-ch2:
  38. if !ch2Check {
  39. fmt.Println("ch2Check false")
  40. }
  41. fmt.Println(p)
  42. }
  43. }
  44. }

image.jpeg