golang-based on Timeingwheel timer

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Design ideas

There are several main ways to implement timers under Linux:

    • Implementation of timers based on linked list
    • Implementation of timers based on sort list
    • Timer based on minimum heap implementation
    • Timer implementation based on time wheel

The time- wheel-based timer has the lowest time complexity and the most efficient, but we can implement the time-wheel timer through the priority queue .

The implementation of the priority queue can use the maximum heap and the minimum heap, so all data in the queue can define collation auto-ordering. We get the data directly from the function in the queue pop , which is the data we want according to our custom collation.

In the Golang implementation of a priority queue exception is simple, in the container/head package has helped us encapsulate the implementation of the details, we just need to implement a specific interface can be.

Here is an example of what is officially provided

This example demonstrates a priority queue built using the heap interface.//an Item are something we manage in a priori    Ty Queue.type Item struct {value string//The value of the item; arbitrary.    priority int//The item in the queue. The index is needed by update and are maintained by the heap.    Interface methods. index int//index of the item in the heap.} A Priorityqueue implements Heap. Interface and holds Items.type Priorityqueue []*itemfunc (PQ Priorityqueue) len () int {return Len (PQ)}func (PQ Priorityq    Ueue) Less (i, J int) bool {//We want POPs to give us ' the highest, not lowest, priority so We use greater than here. return pq[i].priority > Pq[j].priority}func (PQ priorityqueue) Swap (i, J int) {Pq[i], pq[j] = Pq[j], Pq[i] pq[ I].index = i pq[j].index = J}func (PQ *priorityqueue) Push (x interface{}) {n: = Len (*PQ) Item: = x. (*item) it Em.index = n *PQ = append (*PQ, item)}func (PQ *priorityqueue) Pop () interface{} {old: = *pq N: = Len (old) Item: = old[n-1] Item.index =-1//For safety *PQ = old[0:n-1] ret URN Item}

Because the underlying data structure of the priority queue is constructed from a two-fork tree, we can save each node on the binary tree with an array.
Changing the array requires implementing Go a predefined interface,,, Len Less Swap Push Pop and update .

    • LenInterface Definition Returns queue Length
    • Swapinterface defines queue data precedence, comparison rules
    • PushInterface definition push data to queue operations
    • PopThe interface definition returns the top-level data in the queue and changes the data to delete
    • updateInterface definition updates the data information in the queue

Next we analyze the implementation details in Timeingwheel in the Https://github.com/leesper/tao Open source code.

First, design details

1. Structural details

1.1 Timing Task Structure

type timerType struct {    id         int64    expiration time.Time    interval   time.Duration    timeout    *OnTimeOut    index      int // for container/heap}type OnTimeOut struct {    Callback func(time.Time, WriteCloser)    Ctx      context.Context}

Timertype structure is the abstract structure of timed tasks

    • idUnique ID of the timed task, which can be used to find the scheduled task in the queue
    • expirationThe expiration time of the scheduled task, when the time is up, the execution of the timed task is triggered, and the priority queue is sorted by this field.
    • intervalTrigger frequency of timed tasks, triggered once every interval time period
    • timeoutThis structure holds the timed timeout task, and the task function parameter must conform to the appropriate interface type
    • indexThe subscript of the task that is saved in the queue

1.2 Time Wheel structure

type TimingWheel struct {    timeOutChan chan *OnTimeOut    timers      timerHeapType    ticker      *time.Ticker    wg          *sync.WaitGroup    addChan     chan *timerType // add timer in loop    cancelChan  chan int64      // cancel timer in loop    sizeChan    chan int        // get size in loop    ctx         context.Context    cancel      context.CancelFunc}
    • timeOutChanDefines a cached Chan to save, timed tasks that have been triggered
    • timersis a []*timerType type of slice that saves all scheduled tasks
    • tickerWhen each ticker arrives, the time wheel checks whether the head element in the queue reaches the time-out
    • wgFor concurrency control
    • addChanAdd a task to the queue with a cached Chan
    • cancelChanThe timer stopped Chan
    • sizeChanChan that returns the number of tasks in the queue
    • ctxand cancel user Concurrency control

2. Key function implementations

Main loop function of 2.1 Timingwheel

func (tw *TimingWheel) start() {    for {        select {        case timerID := <-tw.cancelChan:            index := tw.timers.getIndexByID(timerID)            if index >= 0 {                heap.Remove(&tw.timers, index)            }        case tw.sizeChan <- tw.timers.Len():        case <-tw.ctx.Done():            tw.ticker.Stop()            return        case timer := <-tw.addChan:            heap.Push(&tw.timers, timer)        case <-tw.ticker.C:            timers := tw.getExpired()            for _, t := range timers {                tw.TimeOutChannel() <- t.timeout            }            tw.update(timers)        }    }}

The first start function, when creating one TimeingWheel , is performed by one, goroutine start in the start for loop and select to monitor the state of the different channel

    • <-tw.cancelChanReturns the ID of the scheduled task to cancel and deletes it in the queue
    • tw.sizeChan <-Put the number of scheduled tasks into this non-cached channel
    • <-tw.ctx.Done()When the parent context executes cancel, the channel has a value indicating that it is TimeingWheel going to stop
    • <-tw.addChanAdding a task to a queue by Addchan with a cache
    • <-tw.ticker.CTicker timing, when each ticker arrives, the time packet will put the current time into the channel, and when each ticker arrives, the Timeingwheel need to check the queue to the expiration of the task ( tw.getExpired() ), with a range to put TimeOutChannelchannel, and finally in the update queue.

2.2 Timingwheel's Search timeout task function

func (tw *TimingWheel) getExpired() []*timerType {    expired := make([]*timerType, 0)    for tw.timers.Len() > 0 {        timer := heap.Pop(&tw.timers).(*timerType)        elapsed := time.Since(timer.expiration).Seconds()        if elapsed > 1.0 {            dylog.Warn(0, "timing_wheel", nil, "elapsed  %d", elapsed)        }        if elapsed > 0.0 {            expired = append(expired, timer)            continue        } else {            heap.Push(&tw.timers, timer)            break        }    }    return expired}

The data is taken from the queue through a for loop until it is listed as empty or when it encounters the first task that is larger than the task start time append expired . Because the priority queue is expiration sorted according to the
So when you take a task that is not on the first scheduled task, the task that indicates that the scheduled task is not up to the time.

Update queue function for 2.3 timingwheel

func (tw *TimingWheel) update(timers []*timerType) {    if timers != nil {        for _, t := range timers {            if t.isRepeat() { // repeatable timer task                t.expiration = t.expiration.Add(t.interval)                // if task time out for at least 10 seconds, the expiration time needs                // to be updated in case this task executes every time timer wakes up.                if time.Since(t.expiration).Seconds() >= 10.0 {                    t.expiration = time.Now()                }                heap.Push(&tw.timers, t)            }        }    }}

When the getExpired function takes out the task to be performed in the queue, when some of the scheduled tasks need to be executed continuously, it is necessary to determine whether the scheduled task needs to be re-placed in the priority queue. isRepeatis judged by whether the task is interval greater than 0,
If it is greater than 0, the representation is permanent.

3. Usage of Timeingwheel

To prevent external abuse, blocking the timer association, the framework once again encapsulates the timer this package, called timer_wapper this package, it provides two methods of invocation.

3.1 The first normal invocation of a timed task

func (t *TimerWrapper) AddTimer(when time.Time, interv time.Duration, cb TimerCallback) int64{    return t.TimingWheel.AddTimer(        when,        interv,        serverbase.NewOnTimeOut(t.ctx, func(t time.Time, c serverbase.WriteCloser) {            cb()        }))}
    • AddTimer Add Timer task, task in timer coprocessor execution
    • When is the execution time
    • Interv is the execution cycle, interv=0 executes only once
    • CB as callback function

3.2 The second call to a scheduled task through a task pool

func (t *TimerWrapper) AddTimerInPool(when time.Time, interv time.Duration, cb TimerCallback) int64 {    return t.TimingWheel.AddTimer(        when,        interv,        serverbase.NewOnTimeOut(t.ctx, func(t time.Time, c serverbase.WriteCloser) {            workpool.WorkerPoolInstance().Put(cb)        }))}

The

parameter, like the previous parameter, only uses the task pool in the third parameter, placing the scheduled task in the task pool. The execution of a timed task itself is a put operation.
As for put, that is workers this package is managed. In the worker package, which maintains a task pool, tasks in the task pool are executed in an orderly manner and are easy to manage.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.