1. 程式人生 > >記一個關於std::unordered_map併發訪問的BUG

記一個關於std::unordered_map併發訪問的BUG

前言 =================== 刷題刷得頭疼,水篇blog。這個BUG是我大約一個月前,在做15445實現lock_manager的時候遇到的一個很惡劣但很愚蠢的BUG,排查 + 摸魚大概花了我三天的時間,根本原因是我在使用`std::unordered_map`做併發的時候考慮不周。但由於這個BUG無法在我的本地復現,只能提交程式碼後再gradescope上看到執行日誌,而且列印的日誌還不能太多,因為gradescope的執行比較慢,列印日誌如果稍微多加一點就會報TIMEOUT,所以著實讓我抓狂了一段時間。最後的解決也很突然,非常有戲劇性,可以考慮拿來水點東西。感覺自己寫blog很拖刷leetcode和背八股的節奏,所以找到實習前可能寫不了太多了。 功能描述 ================= 15445 Project4要求我們為`bustub`新增併發訪問控制,實現tuple粒度的**strict 2PL**。簡單來說,一個`RID`表示著一個`tuple`在磁碟上的位置,因此可以唯一標識`tuple`;一個事務就是一個執行緒。事務會併發的對這些`tuple`進行讀寫訪問,因此必須要引入lock來對訪問進行同步,而這個Project要求我們將lock的粒度設定為**tuple**。為了實現這一點,`bustub`設定了一個**單例**的`LockManager`(但其實現方法並不是單例的),要求任何事務在訪問某個RID之前,都要向這個`LockManager`申請鎖。如果事務進行的是讀訪問操作,要呼叫`LockManager`的`lock_shared`方法申請S-LOCK(共享鎖,或者說讀鎖),否則操作就是寫操作,要呼叫`lock_exclusive`方法申請X-LOCK(獨佔鎖,或者說是寫鎖);如果事務已經獲取了S-LOCK,希望在不釋放S-LOCK的情況下將鎖升級為X-LOCK,則要呼叫`lock_upgrade`方法。此外,由於strict 2PL只能保證Serializable Schedule,但無法保證不存在死鎖,因此`LockManager`還需要實現死鎖檢測功能(但這不是本篇blog的重點)。`LockManager`的宣告如下: class LockManager { enum class LockMode { SHARED, EXCLUSIVE, UPGRADE }; enum class RIDLockState { UNLOCKED, S_LOCKED, X_LOCKED }; class LockRequest { public: LockRequest(txn_id_t txn_id, LockMode lock_mode) : txn_id_(txn_id), lock_mode_(lock_mode), granted_(false), aborted_(false) {} txn_id_t txn_id_; LockMode lock_mode_; bool granted_; bool aborted_; }; class LockRequestQueue { public: LockRequestQueue() = default; LockRequestQueue(const LockRequestQueue &rhs) = delete; LockRequestQueue operator=(const LockRequestQueue &rhs) = delete; // DISALLOW_COPY(LockRequestQueue); RIDLockState state_; std::mutex queue_latch_; s