【golang笔记】go_channel(23)

Page content

这一篇简单的整理了golang 管道channel 相关的内容。

1.Goroutine 和 Channel 的关系

Go语言是通过 Goroutine 和 Channel 实现并发编程。
Goroutine 用于执行并发任务,Channel 用于 goroutine 之间的同步、通信。

在Golang的并发哲学里,有一句非常著名的话:

Do not communicate by sharing memory; instead, share memory by communicating. 不要通过共享内存来通信,而要通过通信来实现内存共享。

它依赖CSP(Communication Sequence Process) 模型,简称通信顺序进程。

 -----------               ----------- 
| Goroutine | - Channel - | Goroutine |
 -----------               ----------- 
      |                         |    
    Channel                  Channel
      |                         |
 -----------               ----------- 
| Goroutine | - Channel - | Goroutine |
 -----------               ----------- 

2.全局变量加锁实现

package main

import (
 "fmt"
 "sync"
 "time"
 _ "time"
)

// 需求:现在要计算 1-20 的各个数的阶乘,并且把各个数的阶乘放入到map中。
// 最后显示出来。要求使用goroutine完成

var (
 myMap = make(map[int]int, 10)
 //声明一个全局的互斥锁
 //lock 是一个全局的互斥锁,
 //sync 是包: synchornized 同步
 //Mutex : 是互斥
 lock sync.Mutex
)

// test 函数就是计算 n!, 让将这个结果放入到 myMap
func test(n int) {
 res := 1
 for i := 1; i <= n; i++ {
  res *= i
 }

 //这里我们将 res 放入到myMap
 //加锁
 lock.Lock()
 myMap[n] = res //concurrent map writes?
 //解锁
 lock.Unlock()
}

func main() {
 // 我们这里开启多个协程完成这个任务[200个]
 for i := 1; i <= 20; i++ {
  go test(i)
 }

 //如果不等等,结果计算不到20 有可能到10+就直接进入下一个查询方法了
 //查询方法走完,主线程结束
 time.Sleep(time.Second * 2) 

 //这里我们输出结果,变量这个结果
 lock.Lock()
 for i, v := range myMap {
  fmt.Printf("map[%d]=%d\n", i, v)
 }
 lock.Unlock()
}

go build -race 可查看执行的时候,资源详情。

3.channel 简介

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

4.channel的简答例子

package main

import (
 "fmt"
)

func main() {
 //演示一下管道的使用
 //1. 创建一个可以存放3个int类型的管道
 var intChan chan int
 intChan = make(chan int, 3)

 //2. 看看intChan是什么
 fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)

 //3. 向管道写入数据
 intChan <- 10
 num := 20
 intChan <- num
 intChan <- 30
 //intChan <- 98 //如果超出容量会报deadlock!错

 //4. 看看管道的长度和cap(容量)
 fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3

 //5. 从管道中读取数据
 var num2 int
 num2 = <-intChan
 fmt.Println("num2=", num2)
 fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3

 num3 := <-intChan
 num4 := <-intChan

 //num5 := <-intChan //在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock

 fmt.Println("num3=", num3, "num4=", num4 /*, "num5=", num5*/)
}

5.channel的几个注意事项

  • channel中只能存放指定的数据类型
  • channel的数据放满后,就不能再放入了
  • 虽然满了,但是从channel取数据后是可以继续放入
  • 在没有使用协程的情况下,如果channel数据取完了,再取,就会报deadlock错误。

6.练习

package main

import (
 "fmt"
)

type Cat struct {
 Name string
 Age  int
}

func main() {
 //定义一个存放任意数据类型的管道 3个数据
 //var allChan chan interface{}
 allChan := make(chan interface{}, 3)

 allChan <- 10
 allChan <- "tom jack"
 cat := Cat{"小花猫", 4}
 allChan <- cat

 //我们希望获得到管道中的第三个元素,则先将前2个推出
 <-allChan
 <-allChan

 newCat := <-allChan //从管道中取出的Cat是interface类型

 fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
 //下面的写法是错误的!编译不通过
 //fmt.Printf("newCat.Name=%v", newCat.Name)
 a := newCat.(Cat)
 fmt.Printf("newCat.Name=%v", a.Name)
}

欢迎大家的意见和交流

email: li_mingxie@163.com