1

ARTS

ARTS 是陈浩(网名左耳朵耗子)在极客时间专栏里发起的一个活动,目的是通过分享的方式来坚持学习。

每人每周写一个 ARTS:Algorithm 是一道算法题,Review 是读一篇英文文章,Technique/Tips 是分享一个小技术,Share 是分享一个观点。

本周内容

本周的 ARTS 你将看到:

  1. LeetCode 第 42 题「接雨水」Trapping Rain Water
  2. 这是没有文章推荐的一周。
  3. 关于 Go sync.Map 的一些思考。
  4. 开发者的知识储备和其开发能力中间还隔着无数项目实践和经验。

Algorithm

本周的算法题是 LeetCode 第 42 题「接雨水」—— Trapping Rain Water

我知道这道题各种五花八门的解法,但是如果解法本身太有技巧性的话,反而失去了练习算法题的目的。所以只列举两种我认为最「直观」的解法。

// brute force
func trap_bf(height []int) int {
    ans := 0
    for i := 0; i < len(height); i++ {
        lm, rm := 0, 0
        for l := i; l >= 0; l-- {
            lm = max(lm, height[l])
        }
        for r := i; r < len(height); r++ {
            rm = max(rm, height[r])
        }
        ans += min(lm, rm) - height[i]
    }
    return ans
}


// 类似 DP 的思想
func trap_dp(height []int) int {
    n := len(height)
    if n == 0 {
        return 0
    }
    ans := 0
    lm, rm := make([]int, n), make([]int, n)
    lm[0] = height[0]
    for i := 1; i < n; i++ {
        lm[i] = max(lm[i-1], height[i])
    }
    rm[n-1] = height[n-1]
    for i := n-2; i >= 0; i-- {
        rm[i] = max(rm[i+1], height[i])
    }
    for i := 0; i < n; i++ {
        ans += min(rm[i], lm[i]) - height[i]
    }
    return ans
}

Review 文章推荐

本周没有特别值得推荐的文章。

Tip 编程技巧

接下来我们聊聊关于 Go 语言sync.Map也就是并发安全字典类型的底层实现。

sync.Map这个类型在平时开发中用的并不是非常多,而且在比较早期的版本中 Go 也并没有官方提供一个并发安全的 map. 官方这个做法的原因从普通 map 在检测到并发读写时的报错fatal error: concurrent map writes可见一斑。至于为什么不允许直接并发地使用 map,官方 blog 是这样解释的:

After long discussion it was decided that the typical use of maps did not require safe access from multiple goroutines, and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized. Therefore requiring that all map operations grab a mutex would slow down most programs and add safety to few. This was not an easy decision, however, since it means uncontrolled map access can crash the program.

后来可能因为呼声太高,官方还是提供了一个基于 map 类型的扩展,即sync.Map. 这个扩展的基本思路就是维护两个 map 类型对象:readdirty,读去 map 中的 kv 时尽可能只从 read 中去读,向 map 中写入 kv 的时候会写到 dirty 中。当然为了保持 read 和 dirty 的一致会做一些额外的标记工作。比如,当 dirty 中包含太多 read 中没有的新 kv 的时候就会将 dirty 中的内容直接升级为 read(即将 dirty 的值直接赋给 read 并置 dirty 为 nil)。这样做的最终目的就是分离读写,在并发读取sync.Map中的值的时候无需加锁,在读写并发或者写写并发的时候才会使用锁,从而降低使用锁对性能的影响。

当然,目前实现方式的代价就是sync.Map适合读多写少的情况场景,如果写入占比过大的话就会导致sync.Map性能下降。另外在使用过程中还要尽量关注类型安全。

关于具体的源码解析可以看看下面的这两篇文章,算是比较详细的中文sync.Map源码解读了。

  • 由浅入深的聊聊 Golang 的 sync.Map
    介绍 sync.Map 的实现,也对比了 map 类型,不想自己看源码的话,这篇是很好的替代。因为这篇后半部分都是在罗列源码,对,强迫你看源码。。。
  • Go 1.9 sync.Map揭秘
    内容上和上篇文章互补,可以两篇结合在一起看。

最后,还是建议打开这部分的源码,对照其他文章来看,效率最高。

Share 灵光一闪

最近在学习一些技术实现细节的时候,在感觉到不同的技术确实是「相通」的同时,对于一个开发者真正的「开发能力」是什么这个问题非常迷惑。我想导致这种迷惑的原因很可能是我所谓的「学习」可以提升知识量,但不能真正提升我写代码的能力。这种「写代码的能力」最终还是要通过不停的项目实践才能获得的,单纯的学习只能获得写好代码的前期技能储备,但实践才是无法逃避的能力提升途径。

本周阅读列表


澎湃哥
45 声望6 粉丝