0%

无论是无缓冲通道,还是有缓冲通道,都存在阻塞的情况,教你一招再也不遇到channel阻塞的问题。

这篇文章会介绍,哪些情况会存在阻塞,以及如何使用select解决阻塞。

阻塞场景

阻塞场景共4个,有缓存和无缓冲各2个。

无缓冲通道的特点是,发送的数据需要被读取后,发送才会完成,它阻塞场景:

  1. 通道中无数据,但执行读通道。
  2. 通道中无数据,向通道写数据,但无协程读取。
阅读全文 »

sync.WaitGroup是并发环境中,一个相当常用的数据结构,用来等待所有协程的结束,在写代码的时候都是按着例子的样子写的,也没用深究过它的使用。前几日想着能不能在协程中执行Add()函数,答案是不能,这里介绍下。

陷阱在WaitGroup的3个函数的调用顺序上。先回顾下3个函数的功能:

  1. Add(delta int):给计数器增加delta,比如启动1个协程就增加1。
  2. Done():协程退出前执行,把计数器减1。
  3. Wait():阻塞等待计数器为0。
阅读全文 »

在看《Go入门指南》的一种用闭包处理错误的模式时,里面提到了一种错误的优雅处理方式,减少我们重复写if err:=f(); err != nil{}式的代码,感觉很心动,做了下测试,结论如下:

  1. 能减少if err式的代码,代码可以变清新整洁。
  2. 使用存在限制:只有当错误需要结束调用时才可以使用这种方法,如果被调用函数返回错误,但调用者函数需处理错误后,向下继续执行,则不能采用这种方法。
阅读全文 »

过去在学Actor模型的时候,就认为异步消息是相当的重要,在华为的时候,也深扒了一下当时产品用的消息模型,简单实用,支撑起了很多模块和业务,但也有一个缺点是和其他的框架有耦合,最近看到以太坊的事件框架,同样简单简洁,理念很适合初步接触事件框架的同学,写文介绍一下。

以太坊的事件框架是一个单独的基础模块,存在于目录go-ethereum/event中,它有2中独立的事件框架实现,老点的叫TypeMux,已经基本弃用,新的叫Feed,当前正在广泛使用。

TypeMuxFeed还只是简单的事件框架,与Kafka、RocketMQ等消息系统相比,是非常的传统和简单,但是TypeMuxFeed的简单简洁,已经很好的支撑以太坊的上层模块,这是当下最好的选择。

TypeMuxFeed各有优劣,最优秀的共同特点是,他们只依赖于Golang原始的包,完全与以太坊的其他模块隔离开来,也就是说,你完全可以把这两个事件框架用在自己的项目中。

TypeMux的特点是,你把所有的订阅塞给它就好,事件来了它自会通知你,但有可能会阻塞,通知你不是那么及时,甚至过了一段挺长的时间。

Feed的特点是,它通常不存在阻塞的情况,会及时的把事件通知给你,但需要你为每类事件都建立一个Feed,然后不同的事件去不同的Feed上订阅和发送,这其实挺烦人的,如果你用错了Feed,会导致panic。

接下来,介绍下这种简单事件框架的抽象模型,然后再回归到以太坊,介绍下TypeMuxFeed

阅读全文 »

结论

在Go中,map中存放map,上层map执行delete,子层map占用的内存会释放,无需手动先释放子map内存,再在上层map执行删除。

实验

在C++中,如果使用了map包含map的数据结构,当要释放上层map的某一项时,需要手动释放对应的子map占用的内存,而在Go中,垃圾回收让内存管理变得如此简单。

做2个对比实验,
实验1:普通的map,map保存到是int到int的映射,会执行delete删除map的每一项,执行垃圾回收,看内存是否被回收,map设置为nil,再看是否被回收。
实验2:map套子map,顶层map是int到子map的映射,子map是int到int的映射,同样先执行delete,再设置为nil,分别看垃圾回收情况。

实验1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"log"
"runtime"
)

var lastTotalFreed uint64
var intMap map[int]int
var cnt = 8192

func main() {
printMemStats()

initMap()
runtime.GC()
printMemStats()

log.Println(len(intMap))
for i := 0; i < cnt; i++ {
delete(intMap, i)
}
log.Println(len(intMap))

runtime.GC()
printMemStats()

intMap = nil
runtime.GC()
printMemStats()
}

func initMap() {
intMap = make(map[int]int, cnt)

for i := 0; i < cnt; i++ {
intMap[i] = i
}
}

func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)

lastTotalFreed = m.TotalAlloc - m.Alloc
}

看结果前,解释下几个字段:

  • Alloc:当前堆上对象占用的内存大小。
  • TotalAlloc:堆上总共分配出的内存大小。
  • Sys:程序从操作系统总共申请的内存大小。
  • NumGC:垃圾回收运行的次数。

结果如下:

1
2
3
4
5
6
2018/09/29 20:09:25 Alloc = 65 TotalAlloc = 65  Just Freed = 0 Sys = 1700 NumGC = 0
2018/09/29 20:09:25 Alloc = 387 TotalAlloc = 391 Just Freed = 3 Sys = 3076 NumGC = 1
2018/09/29 20:09:25 8192
2018/09/29 20:09:25 0
2018/09/29 20:09:25 Alloc = 387 TotalAlloc = 392 Just Freed = 1 Sys = 3140 NumGC = 2
2018/09/29 20:09:25 Alloc = 74 TotalAlloc = 394 Just Freed = 314 Sys = 3140 NumGC = 3

Alloc代表了map占用的内存大小,这个结果表明,执行完delete后,map占用的内存并没有变小,Alloc依然是387,代表map的key和value占用的空间仍在map里.执行完map设置为nil,Alloc变为74,与刚创建的map大小基本是约等于。

实验2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main

import (
"log"
"runtime"
)

var intMapMap map[int]map[int]int

var cnt = 1024
var lastTotalFreed uint64 // size of last memory has been freed

func main() {
// 1
printMemStats()

// 2
initMapMap()
runtime.GC()
printMemStats()

// 3
fillMapMap()
runtime.GC()
printMemStats()

// 4
log.Println(len(intMapMap))
for i := 0; i < cnt; i++ {
delete(intMapMap, i)
}
log.Println(len(intMapMap))
runtime.GC()
printMemStats()

// 5
intMapMap = nil
runtime.GC()
printMemStats()
}

func initMapMap() {
intMapMap = make(map[int]map[int]int, cnt)
for i := 0; i < cnt; i++ {
intMapMap[i] = make(map[int]int, cnt)
}
}

func fillMapMap() {
for i := 0; i < cnt; i++ {
for j := 0; j < cnt; j++ {
intMapMap[i][j] = j
}
}
}

func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n",
m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)

lastTotalFreed = m.TotalAlloc - m.Alloc
}

结果

1
2
3
4
5
6
7
2018/09/29 20:10:27 Alloc = 64 TotalAlloc = 64  Just Freed = 0 Sys = 1700 NumGC = 0
2018/09/29 20:10:27 Alloc = 41154 TotalAlloc = 41157 Just Freed = 3 Sys = 46026 NumGC = 5
2018/09/29 20:10:27 Alloc = 41241 TotalAlloc = 41293 Just Freed = 48 Sys = 47082 NumGC = 6
2018/09/29 20:10:27 1024
2018/09/29 20:10:27 0
2018/09/29 20:10:27 Alloc = 114 TotalAlloc = 41295 Just Freed = 41128 Sys = 47082 NumGC = 7
2018/09/29 20:10:27 Alloc = 74 TotalAlloc = 41296 Just Freed = 41 Sys = 47082 NumGC = 8

这个结果表明,在执行完delete后,顶层map占用的内存从41241降到了114,子层map占用的空间肯定是被GC回收了,不然占用内存不会下降这么显著。但依然比初始化的顶层map占用的内存64多出不少,那是因为delete操作,顶层map的key占用的空间依然在map里,当把顶层map设置为nil时,大小变为74吗,顶层map占用那些空间被释放了.

参考资料

  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2018/09/29/go-map-delete/

七牛空间的测试域名现在只能使用一个月了,有天突然发现,博客的图片都无法访问了,因此要为七牛配置自定义的域名,顺便也把博客的自定义域名配上。

看了Github Pages帮助文档,来回跳转导致配置成本增加,记录下简要配置过程,帮助打算配置自定义域名的朋友。

配置步骤

前提:

  1. 已经购买了域名。
  2. Github上已经部署了博客。

域名配置步骤,只需3个:

  1. 获取托管你的博客的Github Pages服务器。ping your_name.github.io,必然是这里列出的某一个
  2. 博客Github仓库页增加CNAME文件,CNAME的配置注意事项见这里, 我的仓库示例在这里
  3. 为你的域名配置解析,让你的DNS能指到Github Pages的服务器IP地址。如果你是阿里云的,参考这个。其中填写的IP就是步骤1中获取的Github Pages的IP地址。
阅读全文 »

Oh Shit!误删数据了。

既然看这篇文章,你必然也有rm命令误删数据的经历了,废话少说,解决办法:使用trash-cli覆盖原有的rm命令,把rm命令更改为RM

需要的软件:

  • trash-cli:会把删除的数据,单独放到程序建立的垃圾桶,可以通过自带的命令查询和恢复。

优点:再也不担心数据丢失了。
缺点:需要手动去清空垃圾桶,是否垃圾占用的空间,还好可以搞个定时任务解决。

阅读全文 »

前言

科学上网,为祖国建设添砖加瓦。

  • 教你浏览器科学上网,获取学习资料。
  • 教你终端科学上网,获取学习资料。

软件列表

  • Shadowsocks:sock5代理
  • proxychains-ng:为终端命令设置SOCKS5代理
阅读全文 »

所谓陷阱,就是它不是你认为的那样,这种认知误差可能让你的软件留下隐藏Bug。刚好Timer就有3个陷阱,我们会讲1)Reset的陷阱和2)通道的陷阱,3)Stop的陷阱与Reset的陷阱类似,自己探索吧。

Reset的陷阱在哪

Timer.Reset()函数的返回值是bool类型,我们看一个问题三连:

  1. 它的返回值代表什么呢?
  2. 我们想要的成功是什么?
  3. 失败是什么?
  • 成功:一段时间之后定时器超时,收到超时事件。
  • 失败:成功的反面,我们收不到那个事件。对于失败,我们应当做些什么,确保我们的定时器发挥作用。

Reset的返回值是不是这个意思?

阅读全文 »

前言

这篇文章从区块传播策略入手,介绍新区块是如何传播到远端节点,以及新区块加入到远端节点本地链的过程,同时会介绍fetcher模块,fetcher的功能是处理Peer通知的区块信息。在介绍过程中,还会涉及到p2p,eth等模块,不会专门介绍,而是专注区块的传播和加入区块链的过程。

当前代码是以太坊Release 1.8,如果版本不同,代码上可能存在差异。

阅读全文 »