互斥锁 Mutex

sync.Mutex.lock()
sync.Mutex.unlock()

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. //需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中
  8. //最后显示出来。要求使用goroutine完成
  9. //思路
  10. //1.编写一个函数,来计算各个数的阶乘,并放入到一个map中
  11. //2.我们启动协程多个,统计的结果放入到map中
  12. //3.map应该做成一个全局的
  13. var (
  14. myMap = make(map[int]int, 10)
  15. //声明一个全局的互斥锁
  16. //lock是一个全局的互斥锁
  17. //sync是包:synchronized
  18. //Mutex互斥
  19. lock sync.Mutex
  20. )
  21. func test(n int) {
  22. res := 1
  23. for i := 1; i <= n; i++ {
  24. res *= i
  25. }
  26. //这里我们将res放入到map中
  27. //加锁
  28. lock.Lock()
  29. myMap[n] = res
  30. //解锁
  31. lock.Unlock()
  32. }
  33. func main() {
  34. //我们这里开启多个协程完成这个任务
  35. for i := 0; i < 20; i++ {
  36. go test(i)
  37. }
  38. time.Sleep(time.Second * 10)
  39. //遍历这个结果
  40. lock.Lock()
  41. for i, v := range myMap {
  42. fmt.Printf("map[%d] = %d\n", i, v)
  43. }
  44. lock.Unlock()
  45. }

channel(管道)-基本介绍

为什么需要channel
前面使用全局变量加锁同步来解决goroutine的通讯,但不完美
1)主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
2)如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这时也会随主线程的退出而销毁
3)通过全局变量加锁同步实现通讯,也并不利用多个协程对全局变量的读写操作
4)赏面种种分析都在呼唤一个新的通讯机制-channel

channel的介绍
1)channel本质就是一个数据结构-队列
2)线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
3)数据是先进先出【FIFO:first in first out】
4)channel是有类型的,一个string的channel只能存放string类型的数据

channel基本使用
var 变量名 chan 数据类型
举例

  1. var intChan chan intintChan用于存放int数据)
  2. var mapChan chan map[int]string maoChan用于存放map[int]string类型)
  3. var perChan chan Person
  4. var perChan2 chan *Person

说明
1)channel是引用类型
2)channel必须初始化才能写入数据,即make后才能使用
3)管道是有类型的,intChan只能写入整数int

  1. package main
  2. import "fmt"
  3. func main() {
  4. //演示一下管道的使用
  5. //1.创建一个可以存放3个int类型的管道
  6. var intChan chan int
  7. intChan = make(chan int, 3)
  8. //2.看看intChan是什么
  9. fmt.Printf("intChan的值=%v intChan本身的地址=%p\n", intChan, &intChan)
  10. //3.向管道写入数据
  11. intChan <- 10
  12. num := 211
  13. intChan <- num
  14. intChan <- 50
  15. //intChan <- 98//注意点,当我们给管道写入数据时,不能超过其容量
  16. //4.看看管道的长度和cap(容量)
  17. fmt.Printf("channel len=%v vap=%v \n", len(intChan), cap(intChan))
  18. //5.从管道中读取数据
  19. var num2 int
  20. num2 = <-intChan
  21. fmt.Println("num2=", num2)
  22. fmt.Printf("channel len=%v vap=%v \n", len(intChan), cap(intChan))
  23. //6.在没有使用协程的情况下,如果我们的管道数据已经全部取出,再去就会报错 deadlock
  24. }

channel的遍历和关闭

channnel的关闭
close(管道)
channel的遍历
channel支持for-range的方式进行遍历,请注意两个细节
在遍历时,如果channel没有关闭,则会出现deadlock的错误
在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历

应用

  1. package main
  2. import "fmt"
  3. func writeDate(intChan chan int) {
  4. for i := 0; i < 50; i++ {
  5. intChan <- i
  6. }
  7. close(intChan)
  8. }
  9. func readDate(intChan chan int, exitChan chan bool) {
  10. for {
  11. v, ok := <-intChan
  12. if !ok {
  13. break
  14. }
  15. fmt.Println("读取到数据", v)
  16. }
  17. exitChan <- true
  18. close(exitChan)
  19. }
  20. func main() {
  21. //创建两个管道
  22. intChan := make(chan int, 50)
  23. exitChan := make(chan bool, 1)
  24. go writeDate(intChan)
  25. go readDate(intChan, exitChan)
  26. for {
  27. _, ok := <-exitChan
  28. if !ok {
  29. break
  30. }
  31. }
  32. }

练习
要求:
1)启动一个协程,将1-200的数放入到一个channel中,比如numChan
2)启动8个协程,从numChan取出数(比如n),并计算1+…+n的值,并存放到resChan
3)最后8个协程协同完成工作后,再遍历resChan,显示结果【如res[1]=1…res[10]=55…】
4)注意:考虑resChan chan int是否合适

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. //定义一个wg,数据类型为WaitGrop,在关闭协程时,需要被用到
  7. var wg sync.WaitGroup
  8. //编写writeData函数
  9. func wtireData(intChan chan<- int) {
  10. for i := 1; i <= 200; i++ {
  11. intChan <- i
  12. }
  13. close(intChan)
  14. wg.Done()
  15. }
  16. //编写sumData函数
  17. func sumData(intChan <-chan int, resChan chan<- map[int]int) {
  18. for {
  19. v, ok := <-intChan
  20. if !ok {
  21. break
  22. }
  23. //计算1+2+...n的值
  24. sum := 0
  25. for i := 1; i <= v; i++ {
  26. sum += i
  27. }
  28. //将值发送至resChan
  29. numSum := make(map[int]int)
  30. numSum[v] = sum
  31. resChan <- numSum
  32. }
  33. //close(resChan)
  34. wg.Done()
  35. }
  36. //编写readData函数遍历输出
  37. func readData(resChan <-chan map[int]int) {
  38. for {
  39. v, ok := <-resChan
  40. if !ok {
  41. break
  42. }
  43. for index, value := range v {
  44. fmt.Printf("res[%v]=%v\n", index, value)
  45. }
  46. }
  47. wg.Done()
  48. }
  49. //主函数
  50. func main() {
  51. intChan := make(chan int, 10)
  52. resChan := make(chan map[int]int, 10) //根据题目要求我们这里使用map存储计算的值
  53. //指定当前程序使用协程的个数,你启用多少就填多少,不能多也不能少
  54. wg.Add(9)
  55. go wtireData(intChan)
  56. //启用8个协程处理问题
  57. for i := 1; i <= 8; i++ {
  58. go sumData(intChan, resChan)
  59. go readData(resChan)
  60. }
  61. wg.Wait()
  62. }

阻塞案例

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. var wg sync.WaitGroup
  7. func readData(intChan chan int) {
  8. for i := 0; i < 50; i++ {
  9. intChan <- i
  10. //time.Sleep(time.Second)
  11. }
  12. close(intChan)
  13. wg.Done()
  14. }
  15. func writeData(intChan chan int) {
  16. for i := range intChan {
  17. fmt.Println(i)
  18. }
  19. wg.Done()
  20. }
  21. func main() {
  22. intChan := make(chan int, 10)
  23. wg.Add(2)
  24. go readData(intChan)
  25. go writeData(intChan)
  26. wg.Wait()
  27. }

协程求素数

image.png

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. var wg = sync.WaitGroup{}
  7. func writeData(intChan chan int) {
  8. for i := 1; i < 20000; i++ {
  9. intChan <- i
  10. }
  11. close(intChan)
  12. wg.Done()
  13. }
  14. func prime(intChan chan int, resultChan chan int) {
  15. for num := range intChan {
  16. flag := false
  17. for i := 2; i <= num/2; i++ {
  18. if num%i == 0 {
  19. flag = true
  20. break
  21. }
  22. }
  23. if !flag {
  24. resultChan <- num
  25. fmt.Println(num)
  26. }
  27. }
  28. wg.Done()
  29. }
  30. func main() {
  31. wg.Add(5)
  32. intChan := make(chan int, 1000)
  33. resultChan := make(chan int, 10000)
  34. go writeData(intChan)
  35. for i := 0; i < 4; i++ {
  36. go prime(intChan, resultChan)
  37. }
  38. wg.Wait()
  39. }

channel使用细节

  • 管道可以声明为只读或者只写 ```go //默认情况下,管道是双向的 var chan1 chan int

//声明为只写 var chan2 chan <- int

//声明为只读 car chan3 <-chan int

  1. - 使用select可以解决管道阻塞问题
  2. ```go
  3. package main
  4. import (
  5. "fmt"
  6. "time"
  7. )
  8. func main() {
  9. //使用select可以解决从管道取数据阻塞问题
  10. //1.定义一个管道 10个数据int
  11. intChan := make(chan int, 10)
  12. for i := 0; i < 10; i++ {
  13. intChan <- i
  14. }
  15. //2.定义一个管道 5个数据string
  16. stringChan := make(chan string, 5)
  17. for i := 0; i < 5; i++ {
  18. stringChan <- "hello" + fmt.Sprintf("%d", i)
  19. }
  20. //传统的方法在遍历管道时,如果不关闭会阻塞导致deadlock
  21. //问题:在实际开发中,我们不好确定什么时候关闭该管道
  22. //可以使用select方式解决
  23. label:
  24. for {
  25. select {
  26. //注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock
  27. //会自动到下一个case匹配
  28. case v := <-intChan:
  29. fmt.Printf("从intChan读取的数据%d\n", v)
  30. time.Sleep(time.Second)
  31. case v := <-stringChan:
  32. fmt.Printf("从stringChan读取到数据%s\n", v)
  33. time.Sleep(time.Second)
  34. default:
  35. fmt.Println("都取不到了")
  36. break label
  37. }
  38. }
  39. }
  • goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题 ```go package main

import ( “fmt” “sync” “time” )

var wg sync.WaitGroup

func sayHello() { for i := 0; i < 10; i++ { time.Sleep(time.Second) fmt.Println(“hello world”) } wg.Done() }

func test() { //我们这里可以使用defer+recover defer func() { //捕获test抛出panic if err := recover(); err != nil { fmt.Println(“test()发生错误”, err) wg.Done() } }()

  1. var myMap map[int]string
  2. myMap[0] = "golang"
  3. wg.Done()

}

func main() { wg.Add(2) go sayHello() go test() wg.Wait()

}

```