作者:看那个码农
公众号:看那个码农
上期内容介绍了Go语言学习之键盘输入与随机数|Go主题月
- 键盘输入
- 随机数
本篇内容将继续带领大家走进Go语言的世界。
1.本文简介
Go语言学习之使用channel
2.channel的来源
在Go语言中,单纯将函数并发执行是没有意义的。函数与函数之间需要交换数据才能体现并发执行函数的意义。
虽然在Go语言程序中可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。
因此,Go语言提倡使用通信的方式代替共享内存,这里通信的方法就是使用通道channel。
使用通道channel实现多个goroutine间的通信。
3.channel的特性
Go语言中的通道channel是一种特殊的类型。
在任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。
通道像一个传送带或者队列,遵循先入先出的规则。
4.创建channel类型
4.1 声明channel类型
声明channel类型的语法格式如下:
var channel 变量 chan channel类型
- channel变量:保存通道内的变量。
- channel类型:通道内的数据类型。
chan类型的空值是nil,声明后需要配合make后才能进行使用。
4.2 创建channel类型
具体channel类型的创建格式如下:
通道示例 := make(chan 数据类型)
用法如下:
//创建一个整数类型channel
ch1 := make(chan int)
//创建一个空接口类型channel
ch2 := make(chan interface{})
5.使用channel发送数据
通过channel发送数据需要使用特殊的操作符“<-”
用法如下:
channel 变量 <-值
channel发送值的类型必须与channel的元素类型一致。
如果接收方一直没有接收,那么发送操作将持续阻塞。
6.使用channel接收数据
channel收发操作是在不同的两个goroutine间进行。
方式有以下4种:
6.1 阻塞接收数据
channel接收使用特殊的操作符“<-”
用法如下:
data := <-ch
执行该语句时channel将会阻塞,直到接收到数据并赋值给data变量。
代码案例如下:
package main
import "fmt"
func sendData(ch1 chan string) {
defer close(ch1)
for i:=0;i<3;i++{
ch1 <- fmt.Sprintf("发送数据%v\n",i)
}
}
func main() {
ch1 :=make(chan string)
go sendData(ch1)
for{
data := <-ch1
if data == ""{
break
}
fmt.Println("从通道中读取数据方式1:",data)
}
}
输出为:
6.2 完整接收数据
阻塞接收数据的完整写法如下所示:
data,ok := <-ch
data表示接收到的数据。未接收到数据时,data为channel类型的零值。ok(布尔类型)表示是否接收到数据。
用法如下:
package main
import "fmt"
func sendData(ch1 chan string) {
defer close(ch1)
for i:=0;i<3;i++{
ch1 <- fmt.Sprintf("发送数据%v\n",i)
}
}
func main() {
ch1 :=make(chan string)
go sendData(ch1)
for{
data,ok := <-ch1
fmt.Println(ok)
if !ok{
break
}
fmt.Println("从通道中读取数据方式2:",data)
}
}
输出为:
6.3 忽略接收数据
接收任意数据,忽略接收的数据。
用法如下:
<-ch
执行该语句时channel将会阻塞。该语句目的不在于接收channel中的数据,而是为了阻塞goroutine。
6.4 循环接收数据
循环接收数据,需要配合使用关闭channel,借助普通for循环和for…range语句循环接收多个元素。遍历channel,遍历到的结果就是接收到的数据,数据类型就是channel的数据类型。
普通for循环接收channel数据,需要有break循环的条件;for…range会自动判断出channel已关闭,而无须通过判断来终止循环。
用法如下:
package main
import "fmt"
func sendData(ch1 chan string) {
defer close(ch1)
for i:=0;i<3;i++{
ch1 <- fmt.Sprintf("发送数据%v\n",i)
}
}
func main() {
ch1 :=make(chan string)
go sendData(ch1)
for value:=range ch1{
fmt.Println("从通道中读取数据方式3:",value)
}
}
输出为:
7.阻塞
channel默认是阻塞的。当数据被发送到channel时会发生阻塞,直到有其它goroutine从该channel中读取数据。当从channel读取数据时,读取也会被阻塞,直到其它goroutine将数据写入该channel。
用法如下:
package main
import "fmt"
func main() {
ch1 :=make(chan int)
fmt.Printf("%T\n",ch1)
ch2 :=make(chan bool)
go func() {
data,ok := <- ch1
if ok{
fmt.Println("子goroutine取到的数值为:",data)
}
ch2 <-true
}()
ch1<-10
<-ch2
fmt.Println("main over...")
}
输出为:
8.关闭channel
发送方如果数据写入完毕,需要关闭channel,用于通知接收方数据传递完毕。
通常情况是发送方主动关闭channel。接收方通过多重返回值判断channel是否关闭,如果返回值是false,则表示channel已经关闭。
用法如下:
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch)
fmt.Printf("通道的指针:%v\n通道的容量:%v\n通道的长度:%v\n",ch,cap(ch),len(ch))
}
输出为:
往关闭的channel中写入数据会报错。
代码如下:
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch)
fmt.Printf("通道的指针:%v\n通道的容量:%v\n通道的长度:%v\n",ch,cap(ch),len(ch))
ch <-1
}
输出为:
9.缓冲channel
默认创建的channel都是非缓冲channel,读写都是即时阻塞。
带缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞。
因此,可以在非缓冲通道的基础上,为通道增加一个有限大小的存储空间形成带缓冲通道。
9.1 创建带缓冲channel
创建格式如下:
通道实例 := make(chan 通道类型,缓冲大小)
用法如下:
package main
import "fmt"
func main() {
ch := make(chan int,3)
fmt.Println("通道的大小为:",len(ch))
ch<-1
ch<-2
ch<-3
fmt.Println("发送元素到通道后通道的大小为:",len(ch))
}
输出为:
9.2 带缓冲channel与不带缓冲channel对比
缓冲channel与非缓冲channel对比代码案例如下:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
fmt.Println("非缓冲通道",len(ch1),cap(ch1))
go func() {
data := <-ch1
fmt.Println("获得数据",data)
}()
ch1<-100
time.Sleep(time.Second)
fmt.Println("赋值ok","main over...")
ch2 := make(chan string)
fmt.Println("非缓冲通道",len(ch2),cap(ch2))
go sendData(ch2)
for data := range ch2{
fmt.Println("\t 读取数据",data)
}
fmt.Println("main over...")
ch3 := make(chan string,6)
fmt.Println("缓冲通道",len(ch3),cap(ch3))
go sendData(ch3)
for data:=range ch3{
fmt.Println("\t 读取数据",data)
}
fmt.Println("main over...")
}
func sendData(ch chan string) {
for i:=1;i<=3;i++{
ch<-fmt.Sprintf("data%v",i)
fmt.Println("往通道放入数据:",i)
}
defer close(ch)
}
输出为:
10.单向channel
Go语言程序中可以在声明时约束其操作方向,如只发送或是只接收。这种约束方向的通道称为单向通道。
声明格式如下:
只能发送通道: var 通道实例 chan<-元素类型
只能接收通道: var 通道实例 <-chan 元素类型
用法如下:
Ch:=make(chan int)
只能发送通道:
var read chan<-int =ch
只能接收通道:
var recev <-chan =ch
如果你觉得这篇内容对你有帮助的话:
1、点赞支持下吧,让更多的人也能看到这篇内容
2、关注公众号:看那个码农,我们一起学习一起进步。