後端工程師面試考什麼 - Lock 篇

Posted on  Mar 29, 2023  in  後端面試準備  by  Amo Chen  ‐ 5 min read

在程式設計中,平行處理可能會同時訪問共享資源,這樣就可能導致共享資源的競爭與執行結果不一致等等問題。

為了避免這些問題,就需要使用 lock 進行同步,保證在同一時間只有一個執行緒或 process 能夠存取共享資源。

所以在面試中,許多面試官都會考面試者對於 lock 的了解和應用。常見的考題包括 lock 的原理、種類、常見問題和解決方法等。

了解 lock 的使用不僅僅可以應對相關面試問題,也有助於在實際開發中知道如何使用 lock, 以避免平行處理所造成的相關問題。

關鍵區段(Critical section)與共享資源(Shared resource)

在談 Lock 之前,一定要了解何謂 critical section 與 shared resource 。

Critical section 或稱為關鍵區段,指的是一個存取共享資源(例如資料庫中的資料)的程式片段,例如更新使用者存款的程式碼片段,就稱為 critical section ,而使用者存款就是一個共享資源,而這些共享資源有著無法同時被多個執行緒或者 process 存取的特性,同樣舉存款為例,多個執行緒或 process 同時存取存款就可能因為 race condition 導致金額更新之後不正確,詳見 後端工程師面試考什麼 - Race Condition 篇

有了 critical section 與 shared resource 的概念之後,在寫程式的當下就會知道哪邊要上 lock 才安全。

常見的 lock 們

Mutex Lock (互斥鎖)

Mutex lock 是一種常見的 lock, 應用於控制對共享資源的存取。

當一個執行緒或 process A 想要存取一個共享資源時,它必須先獲得 Mutex Lock, 如果另一個執行緒或 process B 已經獲得了 mutex lock, 那麼 A 必須等待 B 釋放該 Lock,才能繼續進行。

通常程式語言都會內建 mutex lock 相關的函式以實現同步(synchronization)。

應用場景

如果你需要保證同一時間只有 1 個執行緒或 process 可以存取共享資源, 那麼使用 mutex lock 準沒錯

Read-Write Lock (讀寫鎖)

Read-Write Lock 也是一種常見的 lock,不過它區分讀取和寫入 2 種操作。

當一個執行緒或 process 想要進行讀取操作時,它可以獲得 read lock, 這樣其他執行緒或 process 也可以繼續進行讀取操作,但不能進行寫入操作。

當一個執行緒或 process 想要進行寫入操作時,它必須獲得 write lock, 這樣其他執行緒或 process 都不能進行讀取或寫入操作。

也由於 Read-Write lock 區分為讀取和寫入 2 種操作,所以實作上也相對較 mutex lock 更為複雜,比較直覺的方式是分別實作一個 read 與 write 的計數器,當 read 計數器不為 0 時,不能進行 write 操作,但是可以進行 read 操作,並且 read 計數器加 1 ,代表正在進行讀取的數量,讀取結束則 read 計數器扣 1 ;反之,當 write 計數器不為 0 時,則不可以進行 read 操作,而且 write 操作必須等 read 計數器為 0 時才能進行。

由於 write 操作必須等 read 計數器為 0 時才能進行,所以很有可能 write 的操作會等非常久甚至等不到,而導致 write 操作遲遲無法進行,因此在實作上也必須考慮 write 操作有較高的優先權,或者 read 優先權較高,或者 2 者沒有任何優先權的保證。

應用場景

Read-Write Lock 可以用於優化對共享資源的存取,例如在多執行緒或多 process 應用程式中,讀取的操作大於寫入的操作時,使用讀寫鎖可以提高應用程式的效率,雖然 write lock 的等待還是省不了,但因為讀取的時候不用等待其他執行緒或 process 釋放 read lock, 因此而提升效率。

Semaphore

Semaphore 可以控制對共享資源的存取數量,譬如設定 Semaphore 為 5 時,就代表同一時間僅有 5 個執行緒或者 process 可以存取共享資源。

它可以被看作是有設定初始值的計數器,以 Semaphore(5) 為例,就代表計數器初始值為 5, 當一個執行緒或 process 想要存取 1 個共享資源時,它必須先獲得 Semaphore, Semaphore 會視現在計數是否大於數值 0 ,如果是,則讓該執行緒獲得 Semaphore, 並且扣掉計數值(-1)以控管存取共享資源的數量,否則該執行緒或 process 則必須等待其他執行緒或 process 釋放 Semaphore 讓計數值大於 0 以獲得 Semaphore 才能繼續存取共享資源。

應用場景

Semaphore 可以用於控制對共享資源的存取,例如資料庫的 connection pool 可以使用 Semaphore 進行實作,藉此控制資料庫的連線數量,也避免多個執行緒或 process 共用同 1 個連線。

使用 Lock 的 2 大常見問題

死結(Deadlock)

死結是指當兩個或多個 process 或執行緒在互相等待對方釋放資源,導致彼此都無法繼續執行的一種狀態。通常 deadlock 會導致系統停擺,造成嚴重的問題。

例如,當 process A 擁有資源 X,但需要資源 Y 才能繼續執行,而process B 擁有資源 Y,但需要資源 X 才能繼續執行,這時就會形成死結。

打破死結的解決方法有很多,例如避免同時持有多個 lock 過久、規定按一定順序獲得 lock 等等,不過實務上死結發生的時機點通常都很隨機,也不太好發現是死結,因此在一開始針對這種必須同時持有多個 lock 的程式就必須特別注意是否會有機率發生死結。

飢餓(Starvation)

Starvation 是指當多個 process 或執行緒競爭同一個鎖時,某些 process 或執行緒一直無法取得鎖,而長時間地停擺,從而無法繼續執行的狀況。

例如 Read-Write lock 提到的 write lock 一直因為 read 計數器遲遲無法歸零所造成的 write 操作一直等待的情況,就是一種 starvation 。

解決 starvation,可以使用一些策略,如導入公平性(fairness)、自旋鎖(spin lock)、時間限制等等。公平性(fairness)可以用排隊的方式給予每個執行緒或 process 機會輪流取得 lock, 是一種能夠確保每個執行緒或 process 都能夠平等取得 lock 的機制,自旋鎖則是一種透過快速反覆嘗試來獲得 lock 的方法,最直覺的方法就是使用 while loop 一直嘗試獲得 lock, 能夠避免某些執行緒或 process 長時間佔用 lock 。

總結

不同的 lock 有不同的使用場景,我們需要根據功能的特點和需求選擇適合的 lock 來保護共享資源,實現對共享資源的同步和保護,提高應用程式的效率和穩定性。

References

https://zh.wikipedia.org/zh-tw/%E8%87%A8%E7%95%8C%E5%8D%80%E6%AE%B5

https://en.wikipedia.org/wiki/Lock_(computer_science)

https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

追蹤新知

看完這篇文章了嗎?還意猶未盡的話,追蹤粉絲專頁吧!

我們每天至少分享 1 篇文章/新聞或者實用的軟體/工具,讓你輕鬆增廣見聞提升專業能力!如果你喜歡我們的文章,或是想了解更多特定主題的教學,歡迎到我們的粉絲專頁按讚、留言讓我們知道。你的鼓勵,是我們的原力!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!