概述

原来分享基础语法的时候,还未分享过 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. Spring Boot2(五):使用Spring Boot结合Thymeleaf模板引擎使用总结

    一.Thymeleaf概述 一般来说,常用的模板引擎有JSP.Velocity.Freemarker.Thymeleaf . SpringBoot推荐的 Thymeleaf – 语法更简单,功能更强大 ...

  2. vscode左边侧边栏字体的大小

    相信很多小伙伴们都会在用vscode的时候,当屏幕大小发生变化的时候,你可能会觉得左边的字体太小了,我也遇到了这样的问题,百度也没有找到解决办法,自己摸索了几天,发现可以通过ctrl+shift+ + ...

  3. 使用 cxf的程序 在win10 测试部署时报空指针异常

    2018-11-08 15:50:55.072 DEBUG 21524 --- [nio-8080-exec-1] o.s.b.w.s.f.OrderedRequestContextFilter  : ...

  4. flutter 如何实现文件读写(使用篇)

    flutter文件读写可以对磁盘文件进行操作,实现某些业务场景,那么我们开始来讲下这个文件读写操作. 使用的库插件(package) dart:io(用于数据处理) path_provider (用于 ...

  5. CSU 1508:地图的四着色(DFS+剪枝)

    http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1508 题意:地图中四联通的块是一个国家,A和B每个人可以涂两种颜色,且B不能涂超过5次,相邻的国家 ...

  6. XML中如果有特殊字符,比如> < 解决方案

    //只需要在代码前加这个就可以了<![CDATA[ 把代码写到这个位置 ]]>

  7. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  8. 哈工大计算机网络Week2-网络应用数据交换

    目录 网络应用数据交换 P2P应用:原理与文件分发 纯P2P架构 文件分发:客户机/服务器 vs. P2P CS 为什么是这样的?不应该传送和发出难道是并行的??? P2P P2P文件分发典型例子:B ...

  9. SpringCloud解析之Zuul(一)

    本文基于Spring Cloud Edgware.SR6,Zuul版本1.3.1,解析Zuul的请求拦截机制,让大家对Zuul的原理有个大概的认识和了解.如有不对的地方,欢迎指正. spring bo ...

  10. Zeppelin0.5.6使用spark解释器

    Zeppelin为0.5.6 Zeppelin默认自带本地spark,可以不依赖任何集群,下载bin包,解压安装就可以使用. 使用其他的spark集群在yarn模式下. 配置: vi zeppelin ...