在golang的上下文中关于withcancel和withtimeout的不同

I'm new in Golang and I have some confused about WithCancel and WithTimeout when I learn golang's context part.

Show the code.

package main

import (
    "context"
    "fmt"
    "time"
)

func someHandler() {
    //ctx, cancel := context.WithCancel(context.Background())
    ctx, cancel := context.WithTimeout(context.Background(),  2*time.Second)
    go doSth(ctx)

    time.Sleep(3 * time.Second)
    cancel()
}

func doSth(ctx context.Context) {
    var i = 1
    for {
        time.Sleep(1 * time.Second)
        select {
        case <-ctx.Done():
            fmt.Println("done")
            return
        default:
            fmt.Printf("work %d seconds: 
", i)
        }
        i++
    }
}

func main() {
    fmt.Println("start...")
    someHandler()
    fmt.Println("end.")
}

Result:

// when use WithCancel
//
start...
work 1 seconds: 
work 2 seconds: 
end.

// when use WithTimeout
start...
work 1 seconds:
done
end.

My question is: why doesn't it print 'done' when I use withCancel but withTimeout does print it?

"Understanding the context package in golang" from Parikshit Agnihotry mentions:

context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)

This function creates a new context derived from the parent context that is passed in.
The parent can be a background context or a context that was passed into the function.

This returns a derived context and the cancel function.
Only the function that creates this should call the cancel function to cancel this context.
You can pass around the cancel function if you wanted to, but, that is highly not recommended. This can lead to the invoker of cancel not realizing what the downstream impact of canceling the context may be. There may be other contexts that are derived from this which may cause the program to behave in an unexpected fashion. In short, NEVER pass around the cancel function.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

context.WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)

This function returns a derived context from its parent that gets cancelled when the deadline exceeds or cancel function is called.

For example, you can create a context that will automatically get canceled at a certain time in future and pass that around in child functions.
When that context gets canceled because of deadline running out, all the functions that got the context get notified to stop work and return.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

context.WithTimeout(parent Context, timeout time.Duration) (ctx Context, cancel CancelFunc)

This function is similar to context.WithDeadline.
The difference is that it takes in time duration as an input instead of the time object.
This function returns a derived context that gets canceled if the cancel function is called or the timeout duration is exceeded.

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))

why dones't print 'done' when use withCancel but withTimeout does

Probably because the Go program already exited before the goroutine has time to acknowledge the "Done" part.