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 binding
context
The deadline at which tasks are cancelled; Returns if no deadline is setok == false
. - Done when binding is current
context
A closed task is returned when the task is canceledchannel
; If the currentcontext
Will not be cancelled and will returnnil
. - Err if
Done
The returnedchannel
Not closed, will returnnil
; ifDone
The returnedchannel
Closed. A non-empty value is returned indicating the reason the task ended. If it iscontext
Been canceled,Err
Will returnCanceled
; If it iscontext
Timeout,Err
Will returnDeadlineExceeded
. - The Value returned
context
Store key value pairs in the currentkey
Corresponding 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