This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

primers

Because of Goroutine, the concurrency of Go is very convenient and the implementation is very simple. Go func() is done.

In the previous article # Handhelds you to Write a Golang coroutine pool, we have compared Java Runnable to Goroutine, which has no arguments, no returns, and no exceptions.

Such three-nothings can cause huge confusion when we perform a time-consuming asynchronous operation. Is it finished? No exceptions, no timeouts. Of course you can use chan to synchronize status, but is there an easier way?

context.Context

Google added context to the standard library in version 1.7. According to the official documentation, context is a global context for a request, carries expiration dates, manual cancellations, etc., and includes a concurrent safe map for carrying data. Does this smell like Java Callable? So let’s first look at the definition of Context

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
Copy the code
  • Deadline returns the current bindingcontextThe deadline at which tasks are cancelled; Returns if no deadline is setok == false.
  • Done when binding is currentcontextA closed task is returned when the task is canceledchannel; If the currentcontextWill not be cancelled and will returnnil.
  • Err ifDoneThe returnedchannelNot closed, will returnnil; ifDoneThe returnedchannelClosed. A non-empty value is returned indicating the reason the task ended. If it iscontextBeen canceled,ErrWill returnCanceled; If it iscontextTimeout,ErrWill returnDeadlineExceeded.
  • The Value returnedcontextStore key value pairs in the currentkeyCorresponding value, if there is no corresponding valuekey, the returnnil(This is all essential oils, that can be used).

How to use?

Let’s reuse the coroutine pool from the previous chapter:

Type GorunTask struct {Id int64 Name String Status string Ctx context. context // Add context Run func() error Callback func(task *GorunTask) Err error }Copy the code

We add context to the task and determine the execution status in the execution method:

func (pool *GorunPool) call(task *GorunTask) { pool.Ticket++ go func() { go func() { task.Status = "running" task.Err = task.Run() task.Status = "exected!" Pool.reschan < -task}() if task.ctx! = nil {for {select {case < -task.ctx.done (): // Ctx calls cancel and is forced to exit task.status = "exit!" task.Err = errors.New("timeout!" ) pool.ResChan <- task return default: time.Sleep(10 * time.Millisecond) } } } }() }Copy the code

The validation test

The TestCase code

Func TestContext(t * test.t) {pool := NewGorunPool(5) job := func() error {time.sleep (10 * time.second) Ensure timeout. Ror ("do thread!" Return nil} callback := func(task *GorunTask) {// callback method t.ror (task.name, "do callback!" , task.Err, task.Status) } ctx1, cancel1 := context.WithCancel(context.TODO()) task1 := NewGorunTaskWithCtx(ctx1, job, Task1.name = "T1" // Pool.execute (task1) defer cancel1() ctx2, cancel2 := context.WithDeadline(context.TODO(), time.Now().Add(3*time.Second)) task2 := NewGorunTaskWithCtx(ctx2, job, Task2.name = "T2" pool.execute (task2) defer cancel2() < -ctx2.done () t.Error("ticker:", "task2 exit", ctx2.Err()) timer := time.NewTicker(5 * time.Second) c := 0 t.Error("start ticker:", c) for { select { case <-ctx1.Done(): c++ t.Error("ticker:", c, "task1 exit") break case <-timer.C: T.ror ("ticker:", c) cancel1()} if c > 0 {break}} time.sleep (1 * time.second)}Copy the code

The execution result

Task 2 exits due to a dealine timeout, and the task 2 callback is called. Then the timer shuts down task 1, and the task 1 callback is executed

How does context.WithDeadline automatically close a task? Let’s look at the execution logic for WithDeadline

WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(d) { // The current deadline is already sooner than the new one. return WithCancel(parent) } c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(false, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } } func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err ! Canceled = nil {c.u.nlock () return // already canceled} // Set the cause of cancellation. C.errr = err Sets a closed channel or closes the done channel. If c.done == nil {c.done = closedchan} else {close(c.done) For child := range c.dren {// NOTE: acquiring the child's lock while holding parent's lock. child.cancel(false, Err)} c.dren = nil c.mu.lock () if removeFromParent {removeChild(c.text, c)}}Copy the code

Write a timer and call the cancel method