golang中的channel到底是什么-1

200 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

1、背景

Golang在并发编程上有两大利器,分别是channel和goroutine,这篇文章我们先聊聊channel。熟悉Golang的人都知道一句名言:“Don’t communicate by sharing memory, share memory by communicating.”。这句话有两层意思,Go语言确实在sync包中提供了传统的锁机制,但更推荐使用channel来解决并发问题。这篇文章会先从channel的用法、channel的原理两部分对channel做一个较为深入的探究 什么是channel

2、什么是channel

从字面上看,channel的意思大概就是管道的意思。channel是一种go协程用以接收或发送消息的安全的消息队列,channel就像两个go协程之间的导管,来实现各种资源的同步。

2.1、以下图片示意channel的用法

1.png

2.2、channel的一般用法

  • channel的创建
// 创建一个类型为int,无缓冲的channel
ch := make(chan int) 
/ 缓冲为1 的channel
ch := make(chan int, 1) 
  • 发送消息
// 将2发送到ch
ch <- 2 
  • 接收消息
// n接收从ch发出的值
n := <- ch 
// 判断是否close
n, ok := <- ch 
if ok {
  fmt.Println(n) // 2
}
  • 关闭管道
close(ch)
  • 阻塞式遍历channel :for …range
ch := make(chan int)    
// 阻塞模式  
for i := range ch {          
  fmt.Println(i)      
}  
  • 非阻塞式遍历channel select
ch := make(chan int)
select {
case res := <-ch:
   fmt.Println(res)
default :
   fmt.Println("无数据")
}
  • 可以判断超时时间的channel
c1 := make(chan string, 1)
go func() {
   time.Sleep(time.Second * 2)
   c1 <- "result 1"
}()
select {
case res := <-c1:
   fmt.Println(res)
case <-time.After(time.Second * 1):
   fmt.Println("timeout 1")
}

2.3、channel使用可能会遇到的问题

fatal error: all goroutines are asleep - deadlock!

  • 死锁1:nil channel 的读写
  • 死锁2:goroutine开启之前使用channel
  • 死锁3 :channel1中调用了channel2,channel2中调用channel1
  • 死锁4:直接读取空channel的死锁
  • 死锁5:超过channel缓冲区继续写入数据导致死锁
  • 死锁6:for range 使用不当导致的死锁

死锁1:nil channel 的读写:

func main() {
  // 空channel 
    var ch chan struct{}
    <-ch
}

死锁2:goroutine开启之前使用channel

func main()  {
  ch := make(chan int)
  ch <- 100  //此处死锁 
  go func() {
    num := <-ch
    fmt.Println("num=", num)
  }()
  //ch <- 100  此处不死锁
  time.Sleep(time.Second*3)
  fmt.Println("finish")
}

死锁3 :channel1中调用了channel2,channel2中调用channel1

func main()  {
  ch1 := make(chan int )
  ch2 := make(chan int )
​
  go func() {
    for {
      select {
        case num := <-ch1:
          fmt.Println("num=", num)
          ch2 <- 100
      }
    }
  }()
​
  for {
    select {
      case num := <-ch2:
        fmt.Println("num=", num)
        ch1 <- 300
    }
  }
}

死锁4:直接读或写 无缓冲channel

func main() {
  ch := make(chan int)
  //close(ch) 向关闭的channel中读取数据 是该数据的类型的零值
  num := <-ch
    ch <- 1
  fmt.Println("num=", num)
}

死锁5:超过channel缓冲区继续写入数据导致死锁

func main() {
  ch := make(chan int, 2)
  ch <- 1
  ch <- 2
  ch <- 3
  num := <-ch
  fmt.Println("num=", num)
}

死锁7:for range 使用不当导致的死锁

以下6中案例可以自己猜测运行结果,是否和自己所想一致

func main() {
​
    // range 1
     pipline := make(chan string)
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
    }()  
    for data := range pipline{
       fmt.Println(data)
    }
    
    // range 2
     pipline := make(chan string)
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
       close(pipline)
    }()
    for data := range pipline{
       fmt.Println(data)
    }
    
    // range 3
    pipline := make(chan string)
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
    }()
    go func() {
       for data := range pipline{
          fmt.Println(data)
       }
       fmt.Println("你能看到我吗")
    }()
    time.Sleep(time.Second)
    fmt.Println("end")
​
​
    // range 4
    pipline := make(chan string)
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
    }()
    
    go func() {
       for data := range pipline {
          fmt.Println(data)
       }
       fmt.Println("你能看到我吗")
      
    }()
     fmt.Println("end")
     select {}
   
    // range 5
    pipline := make(chan string)
    go func() {
       for true{
          time.Sleep(time.Second)
       }
    }()
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
    }()
    go func() {
       for data := range pipline {
          fmt.Println(data)
       }
       fmt.Println("你能看到我吗")
    }()
    select {}
​
   // range 6
    pipline := make(chan string)
    go func() {
       pipline <- "hello world"
       pipline <- "hello China"
       close(pipline)
    }()
    go func() {
       for data := range pipline{
          fmt.Println(data)
       }
       fmt.Println("你能看到我吗")
    }()
    time.Sleep(time.Second)
    fmt.Println("end")
    
    
}

通过以上案例可以说明:

  • 向一个nil channel读写消息 panic
  • 向无缓存的channel发送数据,send 与recv 必须处于2个G 中
  • channel关闭后不可以继续向channel发送消息,但可以继续从channel接收消息;
  • 当channel关闭并且缓冲区为空时,继续从从channel接收消息会得到一个对应类型的零值