go语言lock go语言logo图片

golang语言map的并发和排序golang语言mapgo语言lock的并发和排序
golang缺省go语言lock的map不是thread safe的go语言lock,如果存在读写并发的使用场景 , 必须在外面使用lock机制 。
包sync里面引入一个安全map;
用法:
运行结果如下:
golang官方说法map并不排序,不按key排序 , 也不按插入顺序排序,也就是说map是无序的,无法保证任何排序 。
下面是一种常见的给map排序输出的办法:
Golang 语言深入理解:channel 本文是对 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 中便记录了即将被阻塞的 goroutineG1 ,以及它要发送的数据元素 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 的实现,使用锁的队列实现更简单,容易实现

推荐阅读