golang類比帶逾時的訊號量

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

最近寫項目,需要用到訊號量等待一些資源完成,但是最多等待N毫秒。

在C語言裡,有如下的API來實現帶逾時的訊號量等待:

C
12345 SYNOPSIS     #include <pthread.h>      int     pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

然後在查看golang的document後,發現golang裡並沒有實現帶逾時的訊號量,官方文檔在這裡。

原理

我的業務情境是這樣的:我有一個緩衝字典,當多個使用者請求1個不存在的key時,只有1個請求會穿透到後端,而所有使用者都要排隊等這個請求完成,或者逾時返回。

怎麼實現呢?其實稍微想一想cond的原理,就能類比一個帶逾時的cond出來。

在golang裡,要同時實現”掛起等待”和”逾時返回”,一般得用select case文法,一個case等待阻塞的資源,一個case等待一個timer,這一點是非常確定的。

原本阻塞的資源應該通過條件變數的機制來實現完成通知,既然這裡決定用select case,那麼自然想到用channel來代替這個完成通知。

接下來的問題就是,很多要求者並發來擷取這個資源,但是資源還沒有準備好,所以大家都要排隊並掛起,等待資源完成,並且當資源完成後通知大家。

所以,這裡很自然要為這個資源做一個隊列,每個要求者建立一個chan,並將chan放到隊列裡,接著select case等待這個chan的通知。而另一端,資源完成後遍曆隊列,通知每個chan即可。

最後一個問題是,只有第一個要求者才能穿透請求到後端,而後續要求者不應該穿透重複的請求,這可以通過判斷緩衝裡是否有這個key作為判定首次的條件,而標記位init來判斷要求者是否應該排隊。

我的情境

上面是思路,下面是我的業務情境實現。

C
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 func (cache *Cache) Get(key string, keyType int) *string {if keyType == KEY_TYPE_DOMAIN {key = "#" + key} else {key = "=" + key} cache.mutex.Lock()item, existed := cache.dict[key]if !existed {item = &cacheItem{}item.key = &keyitem.waitQueue = list.New()cache.dict[key] = item}cache.mutex.Unlock() conf := config.GetConfig() lastGet := getCurMs() item.mutex.Lock()item.lastGet = lastGetif item.init { // 已存在並且初始化defer item.mutex.Unlock()return item.value} // 未初始化,排隊等待結果wait := waitItem{}wait.wait_chan = make(chan *string, 1)item.waitQueue.PushBack(&wait)item.mutex.Unlock() // 新增key, 啟動goroutine擷取初始值if !existed {go cache.initCacheItem(item, keyType)} timer := time.NewTimer(time.Duration(conf.Cache_waitTime) * time.Millisecond) var retval *string = nil // 等待初始化完成select {case retval = <- wait.wait_chan:case <- timer.C:}return retval}

簡述一下整個過程:

  • 首先鎖字典,如果key不存在,說明我是第一個要求者,我會建立這個key對應的value,只不過init=false表示它正在初始化。最後,釋放字典鎖。
  • 接下來,鎖住這個key,判斷它已經初始化完成,那麼直接返回value。否則,建立一個chan放入waitQueue等待隊列。最後,釋放key鎖。
  • 接著,如果當前是第一個要求者,那麼會穿透請求到後端(在一個獨立的協程裡去發起網路調用)。
  • 現在,建立一個用於逾時的定時器。
  • 最後,無論當前是否是key的第一個要求者,還是初始化期間的並發要求者,它們都通過select case逾時的等待結果完成。

在initCacheItem函數裡,資料已擷取成功

Go
1234567891011121314 // 一旦標記為init, 後續請求將不再操作waitQueueitem.mutex.Lock()item.value = newValueitem.init = trueitem.expire = expireitem.mutex.Unlock() // 喚醒所有排隊者waitQueue := item.waitQueuefor elem := waitQueue.Front(); elem != nil; elem = waitQueue.Front() {wait := elem.Value.(*waitItem)wait.wait_chan <- newValuewaitQueue.Remove(elem)}

  • 首先,鎖住key,標記init=true,並賦值value,並釋放鎖。此後的請求,都可以立即返回,無需排隊。
  • 之後,因為init=true已被標記,此刻再也有沒有請求會修改waitQueue,所以無需加鎖,直接遍曆隊列,通知其中的每個chan。

最後

這樣就實現了帶逾時的條件變數效果,實際上我的情境是一個broadcast的cond例子,大家可以參照思路實現自己想要的效果,活學活用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.