這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
限速器
之前看到這篇golang並發編程的兩種限速方法,覺得 sleep 等待的方式不是特別好,喚醒線程的時間比較長。而且1s內的請求只能均勻的到來,如瞬間來 N 個, 那麼只有一個能立刻返回,剩下的只能等待。
【修正】
根據的說明,無論是 sleep 還是 chan block, 發生的事情都是 G 和 P 分離,等待下次輪訓,再接著執行,所以可能效能是幾乎一樣的。(這種說法可能不準確,沒有深入)
限速器的作用還是比較重要的,特別是協程使用較多的應用,如果不加限制,可能會OOM。
簡單說下思路:預設時間間隔定為1秒,簡化問題。如果 調用 Limit() bool 介面,如果一秒內運行次數到達某個值,那麼就阻塞, 直到下一個1秒重新計數。
代碼
package libimport ( "sync/atomic" "time")type RateLimiter struct { limit uint64 count uint64 ticker *time.Ticker lockCh chan struct{}}func NewRateLimiter(limit uint64) *RateLimiter { ticker := time.NewTicker(time.Second) r := &RateLimiter{ limit: limit, count: 0, ticker: ticker, lockCh: make(chan struct{}), } go func() { for range ticker.C { if r.count > r.limit { select { case <-r.lockCh: default: r.resetCount() } } if r.count > 0 { r.resetCount() } } }() return r}func (r *RateLimiter) Limit() bool { r.addCount(1) if r.getCount() > r.limit { var s struct{} r.lockCh <- s } return true}func (r *RateLimiter) addCount(interval uint64) { atomic.AddUint64(&r.count, interval)}func (r *RateLimiter) getCount() uint64 { return atomic.LoadUint64(&r.count)}func (r *RateLimiter) resetCount() { atomic.StoreUint64(&r.count, 1)}
測試
package libimport ( "log" "testing" "time")func TestLimit(t *testing.T) { limiter := NewRateLimiter(3) start := time.Now() for i := 0; i < 30; i++ { if limiter.Limit() { log.Printf("i is %d \n", i) } } end := time.Now() d := end.Sub(start) log.Println("spends seconds: ", d.Seconds())}
比較
對比了開篇的sleep實現,limiter設為每秒3次,迴圈30次,用時分別為:
2016/08/31 14:38:26 my limiter spends seconds: 9.00220346s2016/08/31 14:38:35 sleep limiter spends seconds: 9.762009934s
這裡sleep limiter多sleep了兩次,大約 0.67s, 減去這個值等於9.092009934。