Go语言并发编程

Jackey Golang 3,216 次浏览 , , 没有评论

协程

与传统的系统级线程和进程相比,协程的大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常多也不能超过1万个。这也是协程也叫轻量级线程的原因。

goroutine--Go对协程的实现 :

go + 函数名:启动一个协程执行函数体

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "time"
  6. )
  7.  
  8. func test_Routine() {
  9. fmt.Println("This is one routine!!!")
  10. }
  11.  
  12. func Add(x, y int) {
  13. z := x + y
  14. fmt.Println(z)
  15. }
  16.  
  17. func main() {
  18. for i := 1; i < 10; i++ {
  19. go Add(i, i)
  20. }
  21.  
  22. time.Sleep(2)
  23. }

 

channel

Go语言在语言级别提供的goroutine间的通信方式

channel的写与读:

ch <- c 写

c:= <- ch 读

阻塞 除非有goroutine对其进行操作

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "strconv"
  6. )
  7.  
  8. func Add(x, y int, quit chan int) {
  9. z := x + y
  10. fmt.Println(z)
  11.  
  12. quit <- 1
  13. }
  14.  
  15. func Read(ch chan int) {
  16. value := <-ch
  17.  
  18. fmt.Println("value:" + strconv.Itoa(value))
  19. }
  20.  
  21. func Write(ch chan int) {
  22. //ch <- 10
  23. }
  24.  
  25. func main() {
  26. //ch := make(chan int)
  27. //go Read(ch)
  28. //go Write(ch)
  29.  
  30. //time.Sleep(10)
  31.  
  32. //fmt.Println("end of code")
  33.  
  34. chs := make([]chan int, 10)
  35. for i := 0; i < 10; i++ {
  36. chs[i] = make(chan int)
  37. go Add(i, i, chs[i])
  38. }
  39.  
  40. for _, v := range chs {
  41. <-v
  42. }
  43. }

 

缓冲channel

c := make(chan int, n) ,n为缓冲区大小

c := make(chan int) 等价于c:=make(chan int, 0)

缓冲channel的写与读:

ch <- c 写

c:= <- ch 读

缓冲满之后阻塞 除非有goroutine对其进行操作

  1. // 缓冲channel
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8.  
  9. var ch chan int
  10.  
  11. func test_channel() {
  12. ch <- 1
  13. fmt.Println("ch 1")
  14. ch <- 1
  15. fmt.Println("ch 2")
  16. ch <- 1
  17. fmt.Println("come to end goroutine 1")
  18. }
  19.  
  20. func main() {
  21. ch = make(chan int, 0) // 等价于 ch = make(chan int) 都是不带缓冲的channel
  22. ch = make(chan int, 2) // 是带缓冲的channel
  23. go test_channel()
  24. time.Sleep(2 * time.Second)
  25. fmt.Println("running end!")
  26. <-ch
  27.  
  28. time.Sleep(time.Second)
  29. }

 

select

Linux很早就引入的函数,用来实现非阻塞的一种方式

Go语言直接在语言级别支持select关键字,用于处理异步IO 问题

select 的用法与Switch 非常类似,由select 开始一个新的选择模块,每个选择条件由case 语句来描述。

与Switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case 语句必须是一个IO操作。

  1. select {
  2. case <-chan1: // 如果chan1成功读到数据,则进行该case处理语句
  3.  
  4. case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句
  5.  
  6. default: // 如果上面都没有成功,则进入default处理流程
  7. }

 

在一个select 语句中,Go 语言会按顺序从头至尾评估每一个发送和接收的语句。

如果其中的任意一语句可以继续执行(即没有被阻塞),那么久从那些可以执行的语句中任意选择一条来使用。

如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:

  • 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select 语句后的语句中恢复。
  • 如果没有default 语句,那么select 语句将被阻塞,直到至少有一个通信可以进行下去。

注:select 一般不写default,以免程序进入忙轮询。

 

  1. // select.go
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8.  
  9. func main() {
  10. ch := make(chan int)
  11.  
  12. go func(ch chan int) {
  13. ch <- 1
  14. }(ch)
  15.  
  16. time.Sleep(time.Second)
  17.  
  18. select {
  19. case <-ch:
  20. fmt.Print("come to read ch!")
  21. default:
  22. fmt.Print("come to default!")
  23. }
  24. }

 

超时控制的经典实现:

  1. timeout := make(chan bool, 1)
  2. go func() {
  3. time.Sleep(1e9) // 等待1秒钟
  4. timeout <- true
  5. }()
  6.  
  7. // 然后我们把timeout这个channel利用起来
  8. select {
  9. case <-ch: // 从ch中读取到数据
  10. case <-timeout: // 一直没有从ch中读取到数据,但从timeout中读取到了数据
  11. }

 

  1. // select.go
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8.  
  9. func main() {
  10. ch := make(chan int)
  11. timeout := make(chan int, 1)
  12.  
  13. go func() {
  14. time.Sleep(time.Second)
  15. timeout <- 1
  16. }()
  17.  
  18. select {
  19. case <-ch:
  20. fmt.Print("come to read ch!")
  21. case <-timeout:
  22. fmt.Print("come to timeout!")
  23. }
  24.  
  25. fmt.Print("end of code!")
  26. }

 

还可以使用 time.After(...) 作为超时控制

  1. // select.go
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8.  
  9. func main() {
  10. ch := make(chan int)
  11.  
  12. select {
  13. case <-ch:
  14. fmt.Print("come to read ch!")
  15. case <-time.After(time.Second):
  16. fmt.Print("come to timeout!")
  17. }
  18.  
  19. fmt.Print("end of code!")
  20. }

 

协程与线程质的不同

协程的特点:

  • 该任务的业务代码主动要求切换,即主动让出执行权
  • 发生了IO,导致执行阻塞
  1. // goroutine_2.go
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "runtime"
  7. "strconv"
  8. "time"
  9. )
  10.  
  11. func main() {
  12. //协程1
  13. go func() {
  14. for i := 1; i < 100; i++ {
  15. if i == 10 {
  16. //主动出让cpu 使用的话 需要 导入 runtime包
  17. runtime.Gosched()
  18. }
  19. fmt.Println("routine 1:" + strconv.Itoa(i))
  20. }
  21. }()
  22.  
  23. //协程2
  24. go func() {
  25. for i := 100; i < 200; i++ {
  26. fmt.Println("routine 2:" + strconv.Itoa(i))
  27. }
  28. }()
  29.  
  30. time.Sleep(time.Second)
  31. }
  1. // goroutine
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "strconv"
  7. "time"
  8. )
  9.  
  10. var ch chan int
  11.  
  12. func main() {
  13. ch = make(chan int)
  14. //协程1
  15. go func() {
  16. for i := 1; i < 100; i++ {
  17. if i == 10 {
  18. //遇到了阻塞
  19. <-ch
  20. }
  21. fmt.Println("routine 1:" + strconv.Itoa(i))
  22. }
  23. }()
  24.  
  25. //协程2
  26. go func() {
  27. for i := 100; i < 200; i++ {
  28. fmt.Println("routine 2:" + strconv.Itoa(i))
  29. }
  30.  
  31. ch <- 1
  32. }()
  33.  
  34. time.Sleep(time.Second)
  35. }

java的多线程:

  1. public class thread {
  2. public static void main(String[] args){
  3. new Thread(new Test_thread()).start();
  4. new Thread(new Test_thread2()).start();
  5. }
  6. }
  7.  
  8. class Test_thread implements Runnable{
  9. public void run() {
  10. for (int i = 0; i < 100; i++) {
  11. System.out.println("thread 1:" + i);
  12. }
  13. }
  14. }
  15.  
  16. class Test_thread2 implements Runnable{
  17. public void run() {
  18. for (int i = 100; i < 200; i++) {
  19. System.out.println("thread 2:" + i);
  20. }
  21. }
  22. }

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Go