本文是对 Gopher 2017 中一个非常好的 Talk�: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的学习笔记,希望能够通过对 channel 的关键特性的理解,进一步掌握其用法细节以及 Golang 语言设计哲学的管窥蠡测。
channel 是可以让一个 goroutine 发送特定值到另一个 gouroutine 的通信机制。
原生的 channel 是没有缓存的(unbuffered channel),可以用于 goroutine 之间实现同步。
关闭后不能再写入,可以读取直到 channel 中再没有数据,并返回元素类型的零值。
gopl/ch3/netcat3
首先从 channel 是怎么被创建的开始:
在 heap 上分配一个 hchan 类型的对象,并将其初始化,然后返回一个指向这个 hchan 对象的指针。
理解了 channel 的数据结构实现,现在转到 channel 的两个最基本方法: sends 和 receivces ,看一下以上的特性是如何体现在 sends 和 receives 中的:
假设发送方先启动,执行 ch <- task0 :
如此为 channel 带来了 goroutine-safe 的特性。
在这样的模型里, sender goroutine -> channel -> receiver goroutine 之间, hchan 是唯一的共享内存,而这个唯一的共享内存又通过 mutex 来确保 goroutine-safe ,所有在队列中的内容都只是副本。
这便是著名的 golang 并发原则的体现:
发送方 goroutine 会阻塞,暂停,并在收到 receive 后才恢复。
goroutine 是一种 用户态线程 , 由 Go runtime 创建并管理,而不是 *** 作系统,比起 *** 作系统线程来说,goroutine更加轻量。
Go runtime scheduler 负责将 goroutine 调度到 *** 作系统线程上。
runtime scheduler 怎么将 goroutine 调度到 *** 作系统线程上?
当阻塞发生时,一次 goroutine 上下文切换的全过程:
然而,被阻塞的 goroutine 怎么恢复过来?
阻塞发生时,调用 runtime sheduler 执行 gopark 之前,G1 会创建一个 sudog ,并将它存放在 hchan 的 sendq 中。 sudog 中便记录了即将被阻塞的 goroutine G1 ,以及它要发送的数据元素 task4 等等。
接收方 将通过这个 sudog 来恢复 G1
接收方 G2 接收数据, 并发出一个 receivce ,将 G1 置为 runnable :
同样的, 接收方 G2 会被阻塞,G2 会创建 sudoq ,存放在 recvq ,基本过程和发送方阻塞一样。
不同的是,发送方 G1如何恢复接收方 G2,这是一个非常神奇的实现。
理论上可以将 task 入队,然后恢复 G2, 但恢复 G2后,G2会做什么呢?
G2会将队列中的 task 复制出来,放到自己的 memory 中,基于这个思路,G1在这个时候,直接将 task 写到 G2的 stack memory 中!
这是违反常规的 *** 作,理论上 goroutine 之间的 stack 是相互独立的,只有在运行时可以执行这样的 *** 作。
这么做纯粹是出于性能优化的考虑,原来的步骤是:
优化后,相当于减少了 G2 获取锁并且执行 memcopy 的性能消耗。
channel 设计背后的思想可以理解为 simplicity 和 performance 之间权衡抉择,具体如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的实现,使用锁的队列实现更简单,容易实现
用过两种语言,但是没有开发过相同的业务。 但是在当前业务下专门测试过go的性能,不用太担心go的性能问题,而且据说即将发布的go15的会再次对性能有太大的提升。其实还是很喜欢go这个语言,简单,实用,而且适合大规模并发。go的学习门槛要比java要低。如果担心gc影响实时性,也可以自己申请一大块内存自己做内存管理。
大概说,Java的GC更好,Go这方面还不够。
Go的并发更好,Java要同样效果,回调写死了。
真想知道,就自己测。自己设计的测试,更可能体现你想要的指标。
事实上,他们的差别应该小到不需要考虑。
Gorm是Go语言开发用的比较多的一个ORM。它的功能比较全:
但是这篇文章中并不会直接看Gorm的源码,我们会先从database/sql分析。原因是Gorm也是基于这个包来封装的一些功能。所以只有先了解了database/sql包才能更加好的理解Gorm源码。
database/sql 其实也是一个对于mysql驱动的上层封装。”githubcom/go-sql-driver/mysql”就是一个对于mysql的驱动,database/sql 就是在这个基础上做的基本封装包含连接池的使用
下面这个是最基本的增删改查 *** 作
*** 作分下面几个步骤:
因为Gorm的连接池就是使用database/sql包中的连接池,所以这里我们需要学习一下包里的连接池的源码实现。其实所有连接池最重要的就是连接池对象、获取函数、释放函数下面来看一下database/sql中的连接池。
DB对象
获取方法
释放连接方法
连接池的实现有很多方法,在database/sql包中使用的是chan阻塞 使用map记录等待列表,等到有连接释放的时候再把连接传入等待列表中的chan 不在阻塞返回连接。
之前我们看到的Redigo是使用一个chan 来阻塞,然后释放的时候放入空闲列表,在往这一个chan中传入struct{}{},让程序继续 获取的时候再从空闲列表中获取。并且使用的是链表的结构来存储空闲列表。
database/sql 是对于mysql驱动的封装,然而Gorm则是对于database/sql的再次封装。让我们可以更加简单的实现对于mysql数据库的 *** 作。
学习了go的基本的并发变成模式,思路就是一个用通信来共享数据,而并不是像java一样共享内存来通讯。go采用了用channel来传递消息,每一个协程持有一个信道,当信道可用时便可以读写数据,各信道间的处理数据互不影响。
以上就是关于Golang 语言深入理解:channel全部的内容,包括:Golang 语言深入理解:channel、golang并发真的比java高吗、Golang database/sql源码分析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)