这三个 Go 水平自测题,手写不出来还是先老实上班吧

2022-10-27 10:43:01 浏览数 (1)

现在技术文章特别卷,啥啥底层都能给你分析的头头是道,但是分析的对不对要看作者水平,很有可能一个错,抄他的那些人也跟着错,因为我以前看源码的时候就经常感觉自己在两种状态下切换:懂了 / 娘咧漏看了,这个函数干啥的。

八股文这个事儿,其实也特别考验面试官,如果只会一味的问八股文,那也只能说你正巧比面试的人多看点八股,并不能彰显你多有水平,换个小年轻当面试官人家也能干啊。

最近跟以前的老同事聊,他说了个他特别爱问的面试题,我觉得还是挺有水平的,既能引导候选人循序渐进地展开思维,又能考察基础和动手能力。

PS:老哥是83年的,以前在一个广告平台公司 C ,Java,安卓啥的都干过,之前当过我领导,就是他让我刚来公司两星期就去会议室封闭参与用 Go 重构项目的,算是硬逼着我学了学 Go。

老哥说:Go 当初吸引人的地方不就是并发、Channel 这些嘛,其实用过后你会发现也就那样,宣传的有点过了,但是既然平时用 Go 开发,这块就一定得过关,那怎么并发和 Channel 都考察到呢?我一般会问:"Channel 和 并发掌握的熟练吧(一般没人会说不熟)那咱们先用 Channel 实现一个互斥锁",嘿,说你呢,实现一下。

我心想这题我面试别人用过,我背过……,还能难倒我:先初始化一个 capacity 等于 1 的 Channel,它的“空槽”代表锁,哪个协程能成功地把元素发送到这个 Channel,谁就获取了这把锁,给你上代码:

代码语言:javascript复制
// 使用chan实现互斥锁
type Mutex struct {
    ch chan struct{}
}

// 使用锁需要初始化
func NewMutex() *Mutex {
    mu := &Mutex{make(chan struct{}, 1)}
    mu.ch <- struct{}{}
    return mu
}

// 请求锁,直到获取到
func (m *Mutex) Lock() {
    <-m.ch
}

// 解锁
func (m *Mutex) Unlock() {
    select {
    case m.ch <- struct{}{}:
    default:
        panic("unlock of unlocked mutex")
    }
}

老哥说:只要不是太混,这个道题都能答出来,那么接下来我一般会在这道题的基础上两个变种,首先让候选人再扩展一下给这个锁实现 TryLock 功能,TryLock 知道吧,你不是写过两年Java,这个用过吧,你在刚才的基础上实现一下。

我心想:我现在偶尔写Java 的时候都是把以前做的那些项目代码翻出来抄抄,我哪能记得这么清楚,不过这个不就是尝试获取锁,获取不到返回 false 嘛。

这里再给大家解释一下 TryLock 这个功能,下面这段话我从JavaDoc 里抄的:

tryLock() - 可轮询获取锁。如果成功,则返回 true;如果失败,则返回 false。也就是说,这个方法无论成败都会立即返回,获取不到锁(锁已被其他线程获取)时不会一直等待。

那这个也难不倒我啊,咱们学 Channel 的时候,都要学会利用 select chan default 的方式,避免程序阻塞住嘛,那我就套一下这个公式呗。(不过我老不写这个,语法我给忘了,多亏GoLand 提示我半天我才写出来,面试的时候一般在纸上写,咱们读者到时候记得贝贝哈)

代码语言:javascript复制
// 尝试获取锁
func (m *Mutex) TryLock() bool {
    select {
    case <-m.ch:
        return true
    default:
    }
    return false
}

老哥:嗯,确实是这么个解法,不过你这写的也太慢了,算你过吧,其实我直接写估计也写不出来,天天开会手都生了。那好,这个变种如果能答出来,证明这个候选人基础应该还可以,Channel 使用这块应该都没啥问题,那这会儿我就会再扩展一下,让候选人再实现下 TryLock 的重载方法,就是可以设置超时时间那个重载函数,考察一下他定时器这块的知识过不过关,诶,我怎么把答案给说出来了,你懂我意思吧。

老哥的意思就是实现一下 TryWithTimeout,Java 里锁的 Try Lock 还有个重载方法:

tryLock(long, TimeUnit) - 可定时获取锁。和 tryLock() 类似,区别仅在于这个方法在获取不到锁时会等待一定的时间,在时间期限之内如果还获取不到锁,就返回 false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回 true。

因为 Go 里边没有重载方法这种机制咱们就只能写个 TryWithTimeout 方法啦,刚才说了用定时器能实现,不过这块我也忘了怎么用了…...只好默默打开的了浏览器搜索,最后实现答案版本如下:

代码语言:javascript复制
// 加入一个超时的设置
func (m *Mutex) LockTimeout(timeout time.Duration) bool {
    timer := time.NewTimer(timeout)
    select {
    case <-m.ch:
        timer.Stop()
        return true
    case <-timer.C:
    }
    return false
}

最后老哥看了看:嗯,看着挺像那么回事的,今天家里领导有事,我得赶紧接二公主放学了,下回再聊吧。


今天的小短文,主要带大家以一种循序渐进技术问答的形式和我一起回顾下 Channel 并发相关的知识。完整源码已经收录到我整理的Go 开发参考电子书中,给公众号「网管叨bi叨」私信【gocookbook】关键字即可领取,里面收录了很多实用的内容。

平时我们实际应用时最好不要用 Channel 替代sync.Mutex,但是用 Channel 确实除了实现互斥锁的功能外,还能扩充出TryLocKLockTimeout这些扩展功能。

利用 select chan defualt 的方式,很容易实现 TryLockTryLockWithTimeout 的功能。具体来说就是,在 select 语句中,我们可以使用 default 实现 TryLock,再加一个 Timer 来实现 Timeout 的功能。

关于 Channel 和 定时器的基础用法,我瞅了眼以前有写过,如果你才刚接触 Go,可以看下面两篇基础内容:

  • 《Go 语言程序设计》读书笔记 (五) 协程与通道
  • Go语言计时器的使用详解

两篇老文章,内容可以,但是排版非常之垃圾,大家看前得做好心理准备,

0 人点赞