有关golang信道的面试笔记
信道是一个goroutine之间很关键的通信媒介。
理解golang的信道很重要,这里记录平时易忘记的、易混淆的点。
1. 基本使用
刚声明的信道,零值为nil,无法直接使用,需配合make函数进行初始化
ic := make(chan int)
ic <-22 // 向无缓冲信道写入数据
v := <-ic // 从无缓冲信道读取数据
- 无缓冲信道: 一手交钱,一手交货, sender、receiver必须同时做好动作,才能完成发送->接收;否则,先准备好的一方将会阻塞等待。
- 有缓冲信道 make(chan int,10):滑轨流水线,因为存在缓冲空间,故并不强制sender、receiver必须同时准备好;当通道空或满时, 一方会阻塞。
信道存在三种状态: nil, active, closed
针对这三种状态,sender、receiver有一些行为,我也不知道如何强行记忆这些行为 ☹️:
| 动作 | nil | active | closed |
|---|---|---|---|
| close | panic | 成功 | panic |
| ch <- | 死锁 | 阻塞或成功 | panic |
| <-ch | 死锁 | 阻塞或成功 | 零值 |
2. 从1个例子看chan的实质
package main
import (
"fmt"
)
func SendDataToChannel(ch chan int, value int) {
fmt.Printf("ch's value:%v, chan's type: %T \n", ch, ch) // %v 显示struct的值;%T 显示类型
ch <- value
}
func main() {
var v int
ch := make(chan int)
fmt.Printf("ch's value:%v, chan's type: %T \n", ch, ch)
go SendDataToChannel(ch, 101) // 通过信道发送数据
v = <-ch // 从信道接受数据
fmt.Println(v) // 101
}
能正确打印101。
Q1: 刚学习golang的时候,一直给我们灌输golang函数是值传递,那上例在另外一个协程内部对形参的操作,为什么会影响外部的实参?
请关注格式化字符的日志输出:
ch's value:0xc000018180, chan's type: chan int
ch's value:0xc000018180, chan's type: chan int
101
A: 上面的日志显示传递的ch是一个指针值0xc000018180,类型是chan int( 这并不是说ch是指向chan int类型的指针)。
chan int本质就是指向hchan结构体的指针。
内置函数make创建信道: func makechan(t *chantype, size int) *hchan返回了指向hchan结构体的指针:
type hchan struct {
qcount uint // 队列中已有的缓存元素的长度
dataqsiz uint // 环形队列的长度
buf unsafe.Pointer // 环形队列的地址
elemsize uint16
closed uint32
elemtype *_type // 元素类型
sendx uint // 待发送的元素索引
recvx uint // 待接受元素索引
recvq waitq // 阻塞等待的goroutine
sendq waitq // 阻塞等待的gotoutine
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
Q2: 缓冲信道内部为什么要使用环形队列?
A:golang是使用数组来实现信道队列,在不移动元素的情况下, 队列会出现“假满”的情况,
在做成环形队列的情况下, 所有的入队出队操作依旧是 O(1)的时间复杂度,同时元素空间可以重复利用。
需要使用sendIndex,receIndex来标记实际的待插入/拉取位置,显而易见会出现 sendIndex<=receIndex 的情况。
recvq,receq是由链表实现的队列,用于存储阻塞等待的goroutine和待发送/待接收值,
这两个结构也是阻塞goroutine被唤醒的准备条件。
3. 发送/接收的细节
① 不要使用共享内存来通信,而是使用通信来共享内存
元素值从外界进入信道会被复制,也就是说进入信道的是元素值的副本,并不是元素本身进入信道 (出信道类似)。
金玉良言落到实处:不同的线程不共享内存、不用锁,线程之间通讯用channel同步也用channel。
发送/接收数据的两个动作(G1,G2,G3)没有共享的内存,底层通过hchan结构体的buf,使用copy内存的方式进行通信,最后达到了共享内存的目的。
② 根据第①点,发送操作包括:复制待发送值,放置到信道内;
接收操作包括:复制元素值, 放置副本到接收方,删除原值,以上行为在全部完成之前都不会被打断。
所以第①点所说的无锁,其实指的业务代码无锁,信道底层实现还是靠锁。
以send操作为例,下面代码截取自 https://github.com/golang/go/blob/master/src/runtime/chan.go#L216
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx) // 计算出buf中待插入位置的地址
if raceenabled {
racenotify(c, c.sendx, nil)
}
typedmemmove(c.elemtype, qp, ep) // 将元素copy进指定的qp地址
c.sendx++ // 重新计算待插入位置的索引
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
一个常规的send动作:
- 计算环形队列的待插入位置的地址
- 将元素copy进指定的qp地址
- 重新计算待插入位置的索引sendx
- 如果待插入位置==队列长度,说明插入位置已到尾部,需要插入首部。
- 以上动作加锁
③ 进入等待状态的goroutine会进入hchan的sendq/recvq列表
调度器将G1、G2置为waiting状态,G1、G2进入sendq列表,同时与逻辑处理器分离;
直到有G3尝试读取信道内recvx元素,之后将唤醒队首G1进入runnable状态,加入调度器的runqueue。
这里面涉及gopark, goready两个函数。
如果是无缓冲信道引起的阻塞,将会直接拷贝G1的待发送值到G2的存储位置
✍️ https://github.com/golang/go/blob/master/src/runtime/chan.go#L527
package main
import (
"fmt"
"time"
)
func SendDataToChannel(ch chan int, value int) {
time.Sleep(time.Millisecond * time.Duration(value))
ch <- value
}
func main() {
var v int
var ch chan int = make(chan int)
go SendDataToChannel(ch, 104) // 通过信道发送数据
go SendDataToChannel(ch, 100) // 通过信道发送数据
go SendDataToChannel(ch, 95) // 通过信道发送数据
go SendDataToChannel(ch, 120) // 通过信道发送数据
time.Sleep(time.Second)
v = <-ch // 从信道接受数据
fmt.Println(v)
time.Sleep(time.Second * 10)
}
Q3:上述代码大概率稳定输出95。
A:虽然4个goroutine被启动的顺序不定,但是肯定都阻塞了,阻塞的时机不一样,被唤醒的是sendq队首的goroutine,基本可认为第三个goroutine被首先捕获进sendq ,因为是无缓冲信道,将会直接拷贝G3的95给到待接收地址。
4. 业内总结的信道的常规姿势
无缓冲、缓冲信道的特征,已经在golang领域形成了特定的套路。
当容量为0时,说明信道中不能存放数据,在发送数据时,必须要求立马有人接收,此时的信道称之为无缓冲信道。
当容量为1时,说明信道只能缓存一个数据,若信道中已有一个数据,此时再往里发送数据,会造成程序阻塞,利用这点可以利用信道来做锁。
当容量大于1时,信道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。
Q4: 为什么无缓冲信道不适合做锁?
A: 我们先思考一下锁的业务实质: 获取独占标识,并能够继续执行; 无缓冲信道虽然可以获取独占标识,但是他阻塞了自身goroutine的执行,所以并不适合实现业务锁。
有关golang信道的面试笔记的更多相关文章
- Java高级开发工程师面试笔记
最近在复习面试相关的知识点,然后做笔记,后期(大概在2018.02.01)会分享给大家,尽自己最大的努力做到最好,还希望到时候大家能给予建议和补充 ----------------2018.03.05 ...
- php面试笔记(5)-php基础知识-自定义函数及内部函数考点
本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 在面试中,考官往往喜欢基础扎实的面试者,而函数相关的考点 ...
- php面试笔记(3)-php基础知识-运算符
本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 在面试中,考官往往喜欢基础扎实的面试者,而运算符相关的考 ...
- php面试笔记(2)-php基础知识-常量和数据类型
本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 面试是每一个PHP初学者到PHP程序员必不可少的一步,冷 ...
- GitHub标星125k!阿里技术官用3个月总结出的24万字Java面试笔记
最近收到一位粉丝的回馈! 这位粉丝已经成功入职阿里了小编很是羡慕啊! 今天就把这份30w字Java面试笔记给大家分享出来,说来也巧这份资料也是由一位阿里技术官整理出来的这算不算是"搬起石头砸 ...
- 前端程序员学习 Golang gin 框架实战笔记之一开始玩 gin
原文链接 我是一名五六年经验的前端程序员,现在准备学习一下 Golang 的后端框架 gin. 以下是我的学习实战经验,记录下来,供大家参考. https://github.com/gin-gonic ...
- 在C#中使用类golang信道编程(一)
BusterWood.Channels是一个在C#上实现的信道的开源库.通过使用这个类库,我们可以在C#语言中实现类似golang和goroutine的信道编程方式.在这里我们介绍3个简单的信道的例子 ...
- golang实现分布式缓存笔记(一)基于http的缓存服务
目录 前言 cache 缓存服务接口 cache包实现 golang http包使用介绍 hello.go Redirect.go http-cache-server 实现 cacheHandler ...
- golang array, slice, string笔记
本来想写一篇关于golang io的笔记,但是在学习io之前必须了解array, slice, string概念,因此将在下篇写golang io. array: 数组的长度是该数组类型的一部分, ...
随机推荐
- 测试平台系列(92) 让http请求支持文件上传
大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节呢,我们编写了oss的 ...
- busybox+linux Deplay 手机服务器
环境下载地址: Linux Deplay: https://github.com/meefik/linuxdeploy/releases Busybox : https:/ ...
- docker:compose安装
compose:独立于docker的程序,可以做多容器app 安装docker-compose: 下载docker-compose文件: curl -L https://github.com/dock ...
- .Net Core Razor动态菜单实现
准备 1.框架 .netcore 版本 yishaadmin开源框架 2.模板 本文模板使用adminlte3.0,文档地址https://adminlte.io/docs/3.0/ 3.菜单表 关 ...
- 斯坦福NLP课程 | 第1讲 - NLP介绍与词向量初步
作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...
- git-github远程仓库以及git的进阶使用
注意保存自己的笔记吧,看来这个typora还是有点bug,居然还被我碰到了,今天突然死机,重启电脑后,看我自动保存的里面居然没有后来连上网了又有了,这就不说了嘛,但是命名文件有几kb的大小,为什么我一 ...
- 跟我学Python图像处理丨获取图像属性、兴趣ROI区域及通道处理
摘要:本篇文章主要讲解Python调用OpenCV获取图像属性,截取感兴趣ROI区域,处理图像通道. 本文分享自华为云社区<[Python图像处理] 三.获取图像属性.兴趣ROI区域及通道处理 ...
- JavaScript 数据结构与算法2(队列和双端队列)
学习数据结构的 git 代码地址: https://gitee.com/zhangning187/js-data-structure-study 1.队列和双端队列 队列和栈非常类似,但是使用了与 后 ...
- AQS源码三视-JUC系列
AQS源码三视-JUC系列 前两篇文章介绍了AQS的核心同步机制,使用CHL同步队列实现线程等待和唤醒,一个int值记录资源量.为上层各式各样的同步器实现画好了模版,像已经介绍到的ReentrantL ...
- c 语言彩票选号
最近刚学了c语言,就做了个彩票选号程序练手玩玩,做的不好请见谅 1.分为前区(1-35)和后区(1-12)号码 2.先循环随机前区号在循环后区号 3.生成随机时数判断是否有重复值,和之前5期是否出现过 ...