12-Go语言基础-通道(channel)

简介

说道通道:channel,不得不提Go语言的并发编程模型:CSP。
Go语言提倡,通过通信来共享内存。
Goroutines:可以看做是Go的一个并发线程。基于系统线程。
每启动一个Goroutine的堆初始化占用是2/4K,可以扩大到1G。这也是Go语言可以启动成千上万个Goroutine的原因。
启动一个Goroutine只需要再函数调用前加一个关键字go :go myfunc()
通道channel就是作为这些Goroutine之间相互通信、共享数据的渠道,准确的说就是通道。

channel是引用类型,多个名字指向一个channel实例是,一个关闭全部关闭。
channel分为双向通道、只读通道、只写通道。可以通过close()函数关闭通道。
关闭的通道只可以读不可以写(只写的不可读)。

channel带缓冲区和不带缓冲区的区别:
不带缓冲区写入一个在写入就会阻塞。带缓冲区就是比不带缓冲区的多写带的缓冲区大小个数的数据,再写也会阻塞。

使用

  1. 创建channel
    var ch1 chan int创建的ch1是nil值,读取会阻塞,写入会阻塞,close()会panic。除非特殊需求,不要这样创建channel;
    推荐使用make()创建channel。
//双向channel
var ch1=make(chan int)
//只读
var ch2=make(<-chan int)
//只写 
var ch3=make(chan int<-)
package main
import (
	"fmt"

)

func main() {
// 双向不带缓冲区
var ch1 =make(chan int)
// 正常的channel读取写入时,读空会阻塞,写满会阻塞,close()后可读不可写(写close的chanel会panic)。读空会得到0值。
go func(ch chan int){
	for i:=0;i<10;i++{
		ch<-i
	}
	close(ch)
}(ch1)
for{
	fmt.Println("res:",<-ch1)
}
}
=>
这里打印外0~9,会一直打印0,因为ch1被close了,<-ch1读取到的返回值一直是0

这里打印外0~9,会一直打印0,因为ch1被close了,<-ch1读取到的返回值一直是0。

  1. 使用_,ok判断channel是否关闭
    上面的问题怎么改正?使用ok布尔值判断是否关闭。
    读已关闭的channel会得到零值,如果不确定channel,需要使用ok进行检测。ok的结果和含义:
    true:读到数据,并且通道没有关闭。
    false:通道关闭,无数据读到。
for{
	if res,ok:=<-ch1;ok{
		fmt.Println("res:",res)
	}else{
		break
	}
}
=>
res: 0
res: 1
res: 2
res: 3
res: 4
res: 5
res: 6
res: 7
res: 8
res: 9
正常退出。
  1. 使用for range读channel
    使用for range读取channel,读完也会退出。
    场景:当需要不断从channel读取数据时。
    使用for-range读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。
 for res:=range ch1{
 	fmt.Println("res:",res)
 }
  1. select处理多个channel
    select可以同时监控多个通道的情况,只处理未阻塞的case。当通道为nil时,对应的case永远为阻塞,无论读写。特殊关注:普通情况下,对nil的通道写操作是要panic的。
for{
	select{
	case res1:=<-ch1:
			fmt.Println("res1:",res1)
	case res2:=<-ch2:
			fmt.Println("res2:",res2)
	default:
		fmt.Println("break")
		break
	}
}
  1. select中使用超时
for{
	select{
	case res1:=<-ch1:
			fmt.Println("res1:",res1)
	case <-time.After(time.Second):
		break
	}
}
  1. 只读只写通道的作用
    双向通道可以隐身转换为只读或者只写通道。但是只读或者只写通道不能转换为双向或别的通道。
    只读通道可以在只需要读写的函数内使用,防止函数内部误写入。只写也是如此。
package main
import (
	"fmt"
	"time"

)

func main() {
	var ch1=make(chan int)

	go func(ch chan<- int){
		for i:=0;i<10;i++{
			ch<-i
		}
	}(ch1)
	go func(ch <-chan int){
		for res:=range ch{
			fmt.Println("res:",res)
		}
	}(ch1)

	time.Sleep(10*time.Second)
}
=》
res: 0
res: 1
res: 2
res: 3
res: 4
res: 5
res: 6
res: 7
res: 8
res: 9
  1. 使用带缓存的channel控制并发

package main
 
import (
    "time"
    "fmt"

)
 
func Process(ch chan int) {
    //Do some work...
    time.Sleep(time.Second)
 
    ch <- 1 //管道中写入一个元素表示当前协程已结束
}
 
func main() {
    channels := make([]chan int, 10) //创建一个10个元素的切片,元素类型为channel
 
    for i:= 0; i < 10; i++ {
        channels[i] = make(chan int) //切片中放入一个channel
        go Process(channels[i])      //启动协程,传一个管道用于通信
    }
 
    for i, ch := range channels {  //遍历切片,等待子协程结束
        <-ch
        fmt.Println("Routine ", i, " quit!")
    }
}

  1. channel做同步,最简单的生产者消费者
package main

// 带缓冲区的channel

import (
	"fmt"
	"time"
)

func produce(ch chan<- int) {
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("Send:", i)
	}
}

func consumer(ch <-chan int) {
	for i := 0; i < 10; i++ {
		v := <-ch
		fmt.Println("Receive:", v)
	}
}

func main() {
	ch := make(chan int, 10)
	go produce(ch)
	go consumer(ch)
	time.Sleep(1 * time.Second)
}

发布了264 篇原创文章 · 获赞 23 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Edu_enth/article/details/104001236