为啥要一起写呢?

因为chan经常配合着goroutine一起使用,用作于多个goroutine之间的通信

通道(chan)

一、声明通道

package main

import "fmt"

func main() {
    // 声明一个缓冲为10的chan
    chan1 := make(chan string, 10)
    // chan1 len: 0 cap: 10
    fmt.Printf("chan1 len: %d cap: %d", len(chan1), cap(chan1))

    fmt.Println("")

    // 声明一个没有缓冲的chan
    chan2 := make(chan string)
    // chan2 len: 0 cap: 0
    fmt.Printf("chan2 len: %d cap: %d", len(chan2), cap(chan2))
    
    // 声明一个只读通道
    chan3 := make(<-chan string)

    // 声明一个只写通道
    chan4 := make(chan<- string)
}

不带缓冲的通道,进和出都会阻塞。

带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。

二、通道的读 、写和关闭

package main

import "fmt"

func main() {

   // 声明一个10缓冲区的chan
   channel := make(chan int, 10)

   // 往chan里 写入一个数据:1
   channel <- 1

   // 从chan里 读取一个数据
   val := <-channel
   fmt.Println(val) // 1
   
   // 关闭chan
   close(channel)
}

注意:

  • chan的数据只能一个个依次读取,或者写入 。
  • close后不能再写入,但是可以读,但是只读chan不能close。

携程(goroutine)

在Go语言中,协程(goroutine)是一种轻量级的线程,可以在单个Go程序中同时运行数千个协程。协程是通过go关键字创建的。使用协程的好处是可以实现异步执行,提高程序的并发性能。

一、携程的创建

package main

import "fmt"

func printNum() {
   i := 1
   for {
      fmt.Println(i)
      i++
   }
}

func main() {
   
   // 用 go关键字创建一个携程
   go printNum()
   
   
   // 匿名函数的方式
   go func() {
      i := 1
      for {
         fmt.Println(i)
         i++
      }
   }()
}

二、携程间的通信

package main

import (
   "fmt"
   "sync"
)

// 该函数作用是不停的往 chan写数据
func senderData(channel chan<- int, group *sync.WaitGroup) {
   defer group.Done()
   for i := 1; i < 101; i++ {
      channel <- i
      fmt.Printf("senderData往channel写入:%d\n", i)
   }
   close(channel)
}

// 该函数作用是不停的从 chan 读取数据
func receiverData(channel <-chan int, group *sync.WaitGroup) {
   defer group.Done()

   for val := range channel {
      fmt.Printf("receiverData从channel读取到:%d\n", val)
   }
}

func main() {

   channel := make(chan int, 2)

   group := sync.WaitGroup{}

   // 开启一个senderData协程
   group.Add(1)
   go senderData(channel, &group)

   // 开启一个receiverData协程
   group.Add(1)
   go receiverData(channel, &group)

   group.Wait()

}

这段代码很简单,首先写了两个函数:senderDatareceiverData,前者是循环往channel里写1-100,写完就关闭通道,后者是不停的从通道里获取数据。

main函数里,声明了一个缓存为2的通道channel,然后用sync.WaitGroup定义一个协程组,然后每开启一个协程,group就+1,每个协程任务完成自己去调用**Done()**函数从组里删除,当组里协程都完成时,**group.Wait()**就退出阻塞了,整个main函数就结束了。

死锁

其实Go里还是很难出现死锁的,大家都知道Go是天然支持高并发的编程语言,但是保不准有“天才”程序员

(淦!说的就是我自己!上面那端业务代码,我第一次运行的时候就出现死锁了……)

就以上面的代码说明,senderData我们称为生产者receiverData称为消费者,如果channel满了,但是生产者在放里面写数据,或者channel已经空了,但是消费者还在从里获取数据,就会导致协程阻塞,无法继续执行,从而导致死锁。因此在声明channel时给了一定量的缓冲,并且在生产者任务完成时close了channel,从而避免死锁出现。