16.2Go语言干货-并发编程之channel

1.channel

1.1函数与函数之间如何进行通讯?

1.通过共享内存实现函数之间的通讯

共享内存实现函数间的通讯有个极大的弊端,不同的`goroutine`容易发生竞态问题(数据不安全),未来保证数据交换的正确性,必须要给数据进行加锁,这样就会造成性能问题。

2.通过通信共享内存

在Go语言中就是利用`channel` 实现两个`goroutine`之间通过通信共享内存

在Go语言中goroutine是并发的执行体,channel就是执行体之间的连接。
channel可以让一个goroutine发送特定的值到另外一个goroutine的通讯机制。

Go语言中的通道channel是一种特殊的类型,我们可以把这个通道看做成队列,遵循先进先出的规则。

这里要特别说明:每个管道都是一个具体类型的导管,在声明channel的时候需要为其指定元素的类型

1.2 channel类型

channel是一种引用类型,(必要要进行make函数初始化后才能使用

channel声明格式

var 变量 chan 元素类型

举几个栗子

var cha1 chan int64 //声明一个传递64位整型的通道
var cha2 chan bool //声明一个传递布尔的通道
var cha3 chan []int //声明一个传递int切片的通道

1.3 创建channel

chanel通道是一个引用类型,空值为nil

var cha1 chan int
fmt.Println(cha) //nil

** 声明通道后,必须使用make函数初始化后才能使用**

make(chan 元素类型,缓冲大小) //缓冲大小是可选的

举几个栗子

cha1 := make(chan int)
cha2 := make(chan bool)
cha3 := make(chan []int)

1.4 channel的操作

发送(send)、接收(receive)、关闭(close)
发送与接收使用的唯一符号<-

我们先定义一个发送int类型的通道

cha1 := make(chan int)

发送:将一个值发送到通道中

cha1 <- 10 // 把10发送到cha1通道中

接收:变量接收通道的值

n := <- cha1 // 变量n接收通道cha1中的一个值
<- cha1 // 抛弃cha1中的一个值

关闭:通过内置函数close()来关闭通道

close(cha1)

关闭通道需要注意,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

关闭后的通道有以下特点:
1.对一个已经关闭的通道发送值会导致panic
2.对一个已经关闭的通道进行接收会一直获取通道里面的值直到通道为空。
3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。

1.5 无缓冲的通道

无缓冲的通道又名阻塞通道

为什么叫做阻塞通道呢?

package main

var cha1 = make(chan int64)
func main(){
	cha1 <- 10
}

编译通道在执行的时候报错

atal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        G:/go/go_pro/src/github.com/day08/channel/main.go:5 +0x45
exit status 2

这就是无缓冲通道又叫做阻塞通道的原因
我们创建了一个无缓冲通道var cha1 = make(chan int64),无缓冲通道(阻塞通道)只有在有人接收值的时候才能发送值
举个通俗一点的栗子
无缓冲通道就像你住的小区没有快递柜与代收点,快递员必须将这个东西送到你的手里。
无缓冲通道必须有接收值才能发送

package main

import (
	"fmt"
	"sync"
)

var cha1 = make(chan int64)
var wg sync.WaitGroup

func recv1(c chan int64) {
	defer wg.Done()
	n := <-c
	fmt.Println("接收成功", n)
}
func main() {
	wg.Add(1)
	go recv1(cha1)
	cha1 <- 10
	fmt.Println("已经将值发送给通道")
	wg.Wait()
}

1.6 有缓冲通道

举个栗子

package main
func main() {
	cha1 := make(chan int64, 1)
	cha1 <- 10
}

没有报错

1.7 for range从通道中循环取值

当向通道发送完数据后,使用close()关闭通道,使用for range对通道进行循环取值。

当通道被关闭时,在向通道中进行发送值会引发panic,从通道取值的操作会先取完通道中的值,然后取到的是对应类型的零值。

举个栗子

// 开启一个goroutine,将0-100发送给cha1
// 再开启一个goroutine,将cha1中的值平方后发给cha2
// 再开启一个goroutine,在cha2中取值并打印
package main

import (
	"fmt"
	"sync"
	// "time"
)

var wg sync.WaitGroup

func main() {
	cha1 := make(chan int)
	cha2 := make(chan int)
	// 开启一个goroutine,将0-100发送给cha1
	go func() {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			cha1 <- i
		}
		close(cha1)
	}()
	// 开启一个goroutine,在cha1中取值并且平方后发送给cha2
	go func() {
		defer wg.Done()
		for {
			i, ok := <-cha1
			if !ok {
				break
			}
			cha2 <- i * i
		}
		close(cha2)
	}()
	go func() {
		defer wg.Done()
		for i := range cha2 {
			fmt.Println(i)
		}
	}()
	wg.Add(3)
	fmt.Println("main函数")
	wg.Wait()
}

从上面的例子中我们看到有两种方式在接收值的时候判断该通道是否被关闭,不过我们通常使用的是for range的方式。使用for range遍历通道,当通道被关闭的时候就会退出for range。

1.8 单向通道

有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制。

比如限制通道在函数中只能发送或只能接收。

Go语言中提供了单向通道来处理这种情况:

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)
}

其中,
1.chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;
2.<-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。
在函数传参及任何赋值操作中可以将双向通道转换为单向通道,但反过来是不可以的。

2. 通道总结

在这里插入图片描述
关闭已经关闭的channel也会引发panic

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值