這三個(gè) Go 水平自測題,手寫不出來還是先老實(shí)上班吧
現(xiàn)在技術(shù)文章特別卷,啥啥底層都能給你分析的頭頭是道,但是分析的對不對要看作者水平,很有可能一個(gè)錯(cuò),抄他的那些人也跟著錯(cuò),因?yàn)槲乙郧翱丛创a的時(shí)候就經(jīng)常感覺自己在兩種狀態(tài)下切換:懂了 / 娘咧漏看了,這個(gè)函數(shù)干啥的。
八股文這個(gè)事兒,其實(shí)也特別考驗(yàn)面試官,如果只會一味的問八股文,那也只能說你正巧比面試的人多看點(diǎn)八股,并不能彰顯你多有水平,換個(gè)小年輕當(dāng)面試官人家也能干啊。
最近跟以前的老同事聊,他說了個(gè)他特別愛問的面試題,我覺得還是挺有水平的,既能引導(dǎo)候選人循序漸進(jìn)地展開思維,又能考察基礎(chǔ)和動手能力。
PS:老哥是83年的,以前在一個(gè)廣告平臺公司 C++,Java,安卓啥的都干過,之前當(dāng)過我領(lǐng)導(dǎo),就是他讓我剛來公司兩星期就去會議室封閉參與用 Go 重構(gòu)項(xiàng)目的,算是硬逼著我學(xué)了學(xué) Go。
老哥說:Go 當(dāng)初吸引人的地方不就是并發(fā)、Channel 這些嘛,其實(shí)用過后你會發(fā)現(xiàn)也就那樣,宣傳的有點(diǎn)過了,但是既然平時(shí)用 Go 開發(fā),這塊就一定得過關(guān),那怎么并發(fā)和 Channel 都考察到呢?我一般會問:"Channel 和 并發(fā)掌握的熟練吧(一般沒人會說不熟)那咱們先用 Channel 實(shí)現(xiàn)一個(gè)互斥鎖",嘿,說你呢,實(shí)現(xiàn)一下。
我心想這題我面試別人用過,我背過……,還能難倒我:先初始化一個(gè) capacity 等于 1 的 Channel,它的“空槽”代表鎖,哪個(gè)協(xié)程能成功地把元素發(fā)送到這個(gè) Channel,誰就獲取了這把鎖,給你上代碼:
// 使用chan實(shí)現(xiàn)互斥鎖
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")
}
}
老哥說:只要不是太混,這個(gè)道題都能答出來,那么接下來我一般會在這道題的基礎(chǔ)上兩個(gè)變種,首先讓候選人再擴(kuò)展一下給這個(gè)鎖實(shí)現(xiàn) TryLock 功能,TryLock 知道吧,你不是寫過兩年Java,這個(gè)用過吧,你在剛才的基礎(chǔ)上實(shí)現(xiàn)一下。
我心想:我現(xiàn)在偶爾寫Java 的時(shí)候都是把以前做的那些項(xiàng)目代碼翻出來抄抄,我哪能記得這么清楚,不過這個(gè)不就是嘗試獲取鎖,獲取不到返回 false 嘛。
這里再給大家解釋一下 TryLock 這個(gè)功能,下面這段話我從JavaDoc 里抄的:
tryLock() - 可輪詢獲取鎖。如果成功,則返回 true;如果失敗,則返回 false。也就是說,這個(gè)方法無論成敗都會立即返回,獲取不到鎖(鎖已被其他線程獲?。r(shí)不會一直等待。
那這個(gè)也難不倒我啊,咱們學(xué) Channel 的時(shí)候,都要學(xué)會利用 select+chan+default 的方式,避免程序阻塞住嘛,那我就套一下這個(gè)公式唄。(不過我老不寫這個(gè),語法我給忘了,多虧GoLand 提示我半天我才寫出來,面試的時(shí)候一般在紙上寫,咱們讀者到時(shí)候記得貝貝哈)
// 嘗試獲取鎖
func (m *Mutex) TryLock() bool {
select {
case <-m.ch:
return true
default:
}
return false
}
老哥:嗯,確實(shí)是這么個(gè)解法,不過你這寫的也太慢了,算你過吧,其實(shí)我直接寫估計(jì)也寫不出來,天天開會手都生了。那好,這個(gè)變種如果能答出來,證明這個(gè)候選人基礎(chǔ)應(yīng)該還可以,Channel 使用這塊應(yīng)該都沒啥問題,那這會兒我就會再擴(kuò)展一下,讓候選人再實(shí)現(xiàn)下 TryLock 的重載方法,就是可以設(shè)置超時(shí)時(shí)間那個(gè)重載函數(shù),考察一下他定時(shí)器這塊的知識過不過關(guān),誒,我怎么把答案給說出來了,你懂我意思吧。
老哥的意思就是實(shí)現(xiàn)一下 TryWithTimeout,Java 里鎖的 Try Lock 還有個(gè)重載方法:
tryLock(long, TimeUnit) - 可定時(shí)獲取鎖。和 tryLock() 類似,區(qū)別僅在于這個(gè)方法在獲取不到鎖時(shí)會等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還獲取不到鎖,就返回 false。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回 true。
因?yàn)?Go 里邊沒有重載方法這種機(jī)制咱們就只能寫個(gè) TryWithTimeout 方法啦,剛才說了用定時(shí)器能實(shí)現(xiàn),不過這塊我也忘了怎么用了…...只好默默打開的了瀏覽器搜索,最后實(shí)現(xiàn)答案版本如下:
// 加入一個(gè)超時(shí)的設(shè)置
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
}
最后老哥看了看:嗯,看著挺像那么回事的,今天家里領(lǐng)導(dǎo)有事,我得趕緊接二公主放學(xué)了,下回再聊吧。
平時(shí)我們實(shí)際應(yīng)用時(shí)最好不要用 Channel 替代sync.Mutex,但是用 Channel 確實(shí)除了實(shí)現(xiàn)互斥鎖的功能外,還能擴(kuò)充出TryLocK和LockTimeout這些擴(kuò)展功能。
利用 select+chan+defualt? 的方式,很容易實(shí)現(xiàn) TryLock、TryLockWithTimeout? 的功能。具體來說就是,在 select? 語句中,我們可以使用 default? 實(shí)現(xiàn) TryLock?,再加一個(gè) Timer? 來實(shí)現(xiàn) Timeout 的功能。