Golang|channel管道| 8月更文挑战

1,528 阅读3分钟

多线程问题

当多个协程访问同一个资源时,则会出现资源竞争,那么怎么保证并发线程安全,这时候则可以引出channel

为什么需要channel

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

channel介绍

本质是队列,先进先出,线程安全,多个goroutine访问channel线程安全

使用

  • channel是引用类型
  • 必须初始化(make)之后才能使用,
  • 管道有类型

使用close可以关闭channel,这个时候再向其写数据时会报错 panic: send on closed channel,但是可以继续读数据

for-range遍历管道,如果管道未关闭,会出现死锁,如果管道关闭了。则会正常退出循环

未关闭会出现死锁

var intCh chan int
intCh = make(chan int, 3)
intCh <- 10
intCh <- 11
intCh <- 12

for i := range intCh {
   fmt.Println(i)
}

输出:
10
11
12
fatal error: all goroutines are asleep - deadlock!

关闭.正常退出

var intCh chan int
intCh = make(chan int, 3)
intCh <- 10
intCh <- 11
intCh <- 12
close(intCh)
for i := range intCh {
   fmt.Println(i)
}

输出:
10
11
12

同时读写


var (
   ch    = make(chan int, 50)
   flag  = true
)

func main() {
   go write()
   go read()

   for flag {
   }
}

func read() {
   for {
      a, ok := <-ch
      if ok {
         fmt.Println(a)
      } else {
         break
      }
   }

}

func write() {
   for i := 0; i < 50; i++ {
      ch <- i
      fmt.Println("write", i)
      time.Sleep(time.Second)
   }
   close(ch)
   flag = false
}

输出结果:
write 0
0
write 1
1
write 2
2
write 3
3
write 4
4
write 5
5
write 6
6

可以看到,写和读是交替进行的,当管道写完数据,关闭管道后,flag为false,主线程退出

无缓冲的管道

func recv(c chan int) {
	ret := <-c
	fmt.Println("接收成功", ret)
}
func main() {
	ch := make(chan int)
	go recv(ch) // 启用goroutine从通道接收值
	ch <- 10
	fmt.Println("发送成功")
}

如果创建一个无缓冲的管道,向管道中发送数据,必须有接收才能发送。否则会出现死锁

单向通道

限制函数中通道只能接受或者只能发送

func counter(out chan<- int) {
	for i := 0; i < 100; i++ {
		out <- i
	}
	close(out)
}

func squarer(out chan<- int, in <-chan int) {
	for i := range in {
		out <- i * i
	}
	close(out)
}
func printer(in <-chan int) {
	for i := range in {
		fmt.Println(i)
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go counter(ch1)
	go squarer(ch2, ch1)
	printer(ch2)
}
  • chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;
  • <-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。

在函数传参及任何赋值操作中可以将双向通道转换为单向通道,但反过来是不可以的

select方式从未关闭管道取数据

当管道中有数据会走case,全部取完之后则走default

func main() {
   intChan := make(chan int, 10)
   for i := 0; i < 10; i++ {
      intChan <- i
   }
   strChan := make(chan string, 5)
   for i := 0; i < 5; i++ {
      strChan <- "hello" + string(rune(i))
   }

label:
   for {
      select {
      case v := <-intChan:
         fmt.Println(v)
      case s := <-strChan:
         fmt.Println(s)
      default:
         break label
      }
   }
}

使用goroutine注意事项

一旦程序出现panic,整个程序都会崩溃,所以可以使用defer加匿名函数做处理

func test()  {
   defer func() {
      if r := recover(); r != nil {
         fmt.Println("test 错判我")
      }
   }()
   var myMap map[int]string
   myMap[0] = "111"
}