概述

原来分享基础语法的时候,还未分享过 chan 通道,这次把它补上。

chan 可以理解为队列,遵循先进先出的规则。

在说 chan 之前,咱们先说一下 go 关键字。

在 go 关键字后面加一个函数,就可以创建一个线程,函数可以为已经写好的函数,也可以是匿名函数。

举个例子:

func main() {
fmt.Println("main start") go func() {
fmt.Println("goroutine")
}() fmt.Println("main end")
}

输出:

main start
main end

为什么没有输出 goroutine ?

首先,我们清楚 Go 语言的线程是并发机制,不是并行机制。

那么,什么是并发,什么是并行?

并发是不同的代码块交替执行,也就是交替可以做不同的事情。

并行是不同的代码块同时执行,也就是同时可以做不同的事情。

举个生活化场景的例子:

你正在家看书,忽然电话来了,然后你接电话,通话完成后继续看书,这就是并发,看书和接电话交替做。

如果电话来了,你一边看书一遍接电话,这就是并行,看书和接电话一起做。

说回上面的例子,为什么没有输出 goroutine ?

main 函数是一个主线程,是因为主线程执行太快了,子线程还没来得及执行,所以看不到输出。

现在让主线程休眠 1 秒钟,再试试。

func main() {
fmt.Println("main start") go func() {
fmt.Println("goroutine")
}() time.Sleep(1 * time.Second) fmt.Println("main end")
}

输出:

main start
goroutine
main end

这就对了。

接下来,看看如何使用 chan 。

声明 chan

// 声明不带缓冲的通道
ch1 := make(chan string) // 声明带10个缓冲的通道
ch2 := make(chan string, 10) // 声明只读通道
ch3 := make(<-chan string) // 声明只写通道
ch4 := make(chan<- string)

注意:

不带缓冲的通道,进和出都会阻塞。

带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。

写入 chan

ch1 := make(chan string, 10)

ch1 <- "a"

读取 chan

val, ok := <- ch1
// 或
val := <- ch1

关闭 chan

close(chan)

注意:

  • close 以后不能再写入,写入会出现 panic
  • 重复 close 会出现 panic
  • 只读的 chan 不能 close
  • close 以后还可以读取数据

示例

func main() {
fmt.Println("main start")
ch := make(chan string)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}

输出:

main start
fatal error: all goroutines are asleep - deadlock!

What ? 这是为啥,刚开始就出师不利呀?

因为,定义的是一个无缓冲的 chan,赋值后就陷入了阻塞。

怎么解决它?

声明一个有缓冲的 chan。

func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}

输出:

main start
main end

为啥没有输出 a , 和前面一样,主线程执行太快了,加个休眠 1 秒钟,再试试。

func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}

输出:

main start
a
main end

这就对了。

再看一个例子:

func main() {
fmt.Println("main start")
ch := make(chan string)
go func() {
ch <- "a" // 入 chan
}()
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}

输出:

main start
a
main end

再看一个例子:

func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
} func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch) time.Sleep(1 * time.Second)
fmt.Println("main end")
}

输出:

main start
producer start
main end

带缓冲的通道,如果长度等于缓冲长度时,再进就会阻塞。

再看一个例子:

func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
} func customer(ch chan string) {
for {
msg := <- ch
fmt.Println(msg)
}
} func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch)
go customer(ch) time.Sleep(1 * time.Second)
fmt.Println("main end")
}

输出:

main start
producer start
producer end
a
b
c
d
main end

就到这吧。

推荐阅读

gRPC

Gin 框架

基础篇

本文欢迎转发,转发请注明作者和出处,谢谢!

[系列] Go - chan 通道的更多相关文章

  1. Go - chan 通道

    概述 原来分享的基础语法的时候,还未分享过 chan 通道,这次把它补上. chan 可以理解为队列,遵循先进先出的规则. 在说 chan 之前,咱们先说一下 go 关键字. 在 go 关键字后面加一 ...

  2. 转:Java NIO系列教程(五) 通道之间的数据传输

    在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel. transferFro ...

  3. 【练习】goroutine chan 通道 总结

    1. fatal error: all goroutines are asleep - deadlock! 所有的协程都休眠了 - 死锁! package mainimport("fmt&q ...

  4. nio系列(一)---nio重要组成

    nio重要组成部分 前言:通过本文可以了解nio的重要组成部分,了解完基础的内容后后面理解才会简单一点.下一篇会讲讲nio的应用和io的对比.如果有不正确的地方还望指正. channel chanel ...

  5. Go Example--关闭通道

    package main import ( "fmt" ) func main() { jobs := make(chan int, 5) done := make(chan bo ...

  6. [系列] Go gRPC Hello World

    目录 概述 四类服务方法 安装 写个 Hello World 服务 推荐阅读 概述 开始 gRPC 了,这篇文章学习使用 gRPC,输出一个 Hello World. 用 Go 实现 gRPC 的服务 ...

  7. go 学习笔记---chan

    如果说 goroutine 是 Go语言程序的并发体的话,那么 channels 就是它们之间的通信机制.一个 channels 是一个通信机制,它可以让一个 goroutine 通过它给另一个 go ...

  8. Go语言系列之反射

    变量的内在机制 Go语言中的变量是分为两部分的: 类型信息:预先定义好的元信息. 值信息:程序运行过程中可动态变化的. 反射介绍 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量 ...

  9. Gin框架 - 自定义错误处理

    目录 概述 错误处理 自定义错误处理 panic 和 recover 推荐阅读 概述 很多读者在后台向我要 Gin 框架实战系列的 Demo 源码,在这里再说明一下,源码我都更新到 GitHub 上, ...

随机推荐

  1. Sqoop 的基本使用

    目录 一.Sqoop 基本命令 1. 查看所有命令 2. 查看某条命令的具体使用方法 二.Sqoop 与 MySQL 1. 查询MySQL所有数据库 2. 查询指定数据库中所有数据表 三.Sqoop ...

  2. 微信小程序内链微信公众号的方法

    最近接了一个需求,要求在微信小程序内部添加关注微信公众号的方式并给出解决方案,经过几天的翻官网文档,查周边资料,问资深技术员,初步给出两个解决方案: 题外话: 搬砖容易,建设难,搬砖的小伙伴请注明文章 ...

  3. linux下svn安装

    1.环境centos6.4 2.安装svnyum -y install subversion 3.配置 建立版本库目录mkdir /www/svndata svnserve -d -r /www/sv ...

  4. yii框架widget和注册asset的例子

    yii框架是一个基于组件的框架,这样代码的重用性就非常的高,如我们想在网站的多个地方调用编辑器,这样我们就可以自定义一个组件,来供我们调用使用 下面以Ueditor组件为例: 1.下载ueditor到 ...

  5. ubuntu16.04基本设置

    1. ubuntu16.04开启ssh https://jingyan.baidu.com/article/f54ae2fc6f9eef1e93b8497a.html 2. windows 远程桌面连 ...

  6. K Balanced Teams CodeForces - 1133E (Dp)

    题意: 给出 n 个数,选取其中若干个数分别组成至多 k 组,要求每组内最大值与最小值的差值不超过5,求最后被选上的总人数. 题解: 将a[1∼n] 从小到大排序, f[i][j] 表示到第 i 个数 ...

  7. 不懂数据库索引的底层原理?那是因为你心里没点b树

    本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩...   前几天下班回到家后正在处理一个白天没解决的bug,厕所突然传来对象的声音: ...

  8. 1. VMware搭建Linux环境,安装配置centos6.5

    1. 安装VMware,后新建虚拟机 2. 为我们的虚拟机挂载操作系统 3.开启我们的虚拟机,为我们的虚拟机进行安装操作系统 4.配置虚拟机连接网络 修改linux的mac地址 修改mac地址配置文件 ...

  9. HDU 5510:Bazinga(暴力KMP)

    http://acm.hdu.edu.cn/showproblem.php?pid=5510 Bazinga Problem Description   Ladies and gentlemen, p ...

  10. CentOS 6.5 下安装及使用中文输入法

    第一次在本机环境下搭载Linux环境,但搭载后发现在CentOS 6.5只能英文输入,于是试着安装并启用中文输入法,经过一翻折腾,实现了在终端和自带火狐浏览器下支持中文输入法. CentOS下安装中文 ...