GO学习之 通道(Channel)

GO系列

1、GO学习之Hello World
2、GO学习之入门语法
3、GO学习之切片操作
4、GO学习之 Map 操作
5、GO学习之 结构体 操作
6、GO学习之 通道(Channel)

文章目录

  • GO系列
  • 前言
  • 一、Channel 简介
  • 二、初始化通道
    • 2.1 无缓冲通道
    • 2.2 有缓冲通道
    • 2.3 单向通道
  • 三、通道操作
    • 3.1 关闭通道
  • 四、通道异常情况
  • 五、总结

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
现在的互联网项目中,并发量是衡量一个项目的相当重要的指标,如果一个项目不能支持高并发,就好像一辆超跑用了一缸发动机,中看不中用,一点意义都没有啊。
那怎么支持高并发呢,首当其冲的肯定是多线程了,那多线程就势必会带来一个问题,数据的同步和安全!
当然此篇不说多线程,先说说支持多线程之间数据同步的 Channel。
虽然可以用共享内存进行数据交互,但是共享内存在不同的 goroutine 中很容易发生资源竞争的问题,所以为了保证数据交互的正确性,必须使用锁对内存进行加锁,但是这种做法就会带来性能问题了。
Go 语言的并发模型是 CSP(Communicating Sequential Processes),提倡通过通信共享内存实现线程间的数据交互。

一、Channel 简介

Go 语言中的通道(Channel)是一种特殊的类型。见名知意,一边进来另一边出去,或者说通道就像一个队列,总是遵循 先入后出(First In Frist Out) 的规则,以便能保证顺序。
每个通道都是一个具体类型的管道,也就是在声明的时候需要指定元素的数据类型一样。

二、初始化通道

Channel 是一种引用类型,声明通道类型的格式如下:

var 变量名 chan 元素类型

package main

import "fmt"

func main() {
	// 声明一个空的channel
	var ch1 chan int
	fmt.Printf("初始化ch1: %+v\n", ch1)
	ch1 <- 1
	ch1 <- 2
	ch1 <- 3
	fmt.Printf("%+v\n", ch1)
}

运行结果:

PS D:\workspaceGo> go run channelTest.go
初始化ch1: <nil>
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send (nil chan)]:
main.main()
        D:/workspaceGo/channelTest.go:9 +0x73
exit status 2

在声明后的 ch1 接受元素是报错了,因为我们声明的 ch1 其实是一个 nil,向一个空的channel发送元素则会报错。
声明的通道需要使用 make() 函数初始化之后才能使用。

2.1 无缓冲通道

package main

import "fmt"

func main() {
	// 声明并且初始化channel
	ch1 := make(chan int)
	fmt.Printf("初始化ch1: %+v\n", ch1)
	ch1 <- 1
}

运行结果:

PS D:\workspaceGo> go run channelTest.go
初始化ch1: 0xc0000220c0
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        D:/workspaceGo/channelTest.go:9 +0x85
exit status 2

这里为什么会出现 deadlock 错误呢?
答:因为我们创建的是无缓冲通道,无缓冲的通道只有在有其他地方接受值的情况下才能发送值。上面的示例中阻塞在 ch1 <- 1 形成死锁,如何解决呢?我们可以用另一个 goroutine 去接受值,如下:

package main

import "fmt"

func main() {
	// 声明并且初始化channel
	ch1 := make(chan int)
	// 启动一个 gorountine从通道接收值
	go receive(ch1)
	ch1 <- 1
	ch1 <- 2
	ch1 <- 3
	fmt.Println("发送完成!")
}

func receive(c chan int) {
	for i := 0; i < 5; i++ {
		r := <-c
		fmt.Printf("接收到的值为:%+v\n", r)
	}
}

运行结果:

PS D:\workspaceGo> go run channelTest.go
接收到的值为:1
接收到的值为:2
接收到的值为:3
发送完成!

在这个示例中,单独启动一个 goruntine来循环从 ch1 通道中获取值,这样就可以通过 ch1 <- * 不断的往里面放值了。

2.2 有缓冲通道

可以在 make() 函数初始化通道的时候为其指定通道的容量,如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	// 声明并且初始化channel
	ch1 := make(chan int, 5)
	go receive(ch1)
	for i := 0; i < 10; i++ {
		fmt.Printf("向ch1中发送 %d 个元素 \n", i)
		ch1 <- i
	}
	fmt.Println("发送完成!")
	ch1 <- 6
}

func receive(c chan int) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second * 3)
		r := <-c
		fmt.Printf("接收到的值为:%+v\n", r)
	}
}

运行结果:

PS D:\workspaceGo> go run channelTest.go
向ch1中发送 0 个元素 
向ch1中发送 1 个元素
向ch1中发送 2 个元素
向ch1中发送 3 个元素
向ch1中发送 4 个元素
向ch1中发送 5 个元素
接收到的值为:0
向ch1中发送 6 个元素
接收到的值为:1
向ch1中发送 7 个元素
接收到的值为:2
向ch1中发送 8 个元素
接收到的值为:3
向ch1中发送 9 个元素
接收到的值为:4
发送完成!

初始化通道是制定缓存数量,可以往通道中放入初始化数量以内个数的元素,但是超过了初始化量则会阻塞,等通道中空闲出来

2.3 单向通道

三、通道操作

通道有三种操作:

  • 发送:上面例子中已包含
  • 接受:上面例子中已包含
  • 关闭

3.1 关闭通道

可以通过 close() 函数来关闭 channel(对通道的发送和接受完毕,记得关闭通道),如下:

package main
import (
	"fmt"
)
func main() {
	// 声明并且初始化channel
	ch1 := make(chan int, 1)
	go receive(ch1)
	for i := 0; i < 5; i++ {
		fmt.Printf("向ch1中发送 %d 个元素 \n", i)
		ch1 <- i
	}
	fmt.Println("发送完成!")
	// 关闭通道
	close(ch1)
}
func receive(c chan int) {
	for i := 0; i < 10; i++ {
		r, ok := <-c
		if !ok {
			fmt.Printf("接收到的值为:%+v\n", r)
		} else {
			fmt.Println("通道已关闭!")
		}
	}
}

运行结果:

PS D:\workspaceGo> go run channelTest.go
向ch1中发送 0 个元素 
向ch1中发送 1 个元素
通道已关闭!
向ch1中发送 2 个元素
通道已关闭!
向ch1中发送 3 个元素
通道已关闭!
向ch1中发送 4 个元素
通道已关闭!
发送完成!

初始化通道,缓冲为 1 ,当向 ch1 通道中发送元素后,就立马关闭 通道,在 receive() 函数中只能接受到部分元素。

四、通道异常情况

操作 nil 非空 没满
接受 deadlock 接受成功 阻塞 接受成功 接受成功
发送 deadlock 发送成功 发送成功 阻塞 发送成功
关闭 panic 关闭成功,关闭后,接受到 0 值 关闭成功,接收到 0 值 关闭成功,接受到0值 关闭成功,接受到0值

五、总结

通道(Channel)基本的初始化和发送、接受、关闭等操作基本在此篇中体现,使用起来还是间接明了。不像 Java 还需要利用一下锁去控制线程之间的安全问题,虽说是对多线程有较好的支持,但是线程间的数据共享确实实现比较复杂,运用也不简单,需要去深挖。
现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过,如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!

你可能感兴趣的:(golang学习,golang)