猿问

通道和正常关机死锁

运行以下程序并运行CTRL + C,例程在尝试发送到通道但例程已关闭时被阻止。什么是更好的并发设计来解决这个问题?handleprocess


已编辑程序以应用此处建议的规则来描述问题,https://stackoverflow.com/a/66708290/4106031


package main


import (

    "context"

    "fmt"

    "os"

    "os/signal"

    "sync"

    "syscall"

    "time"

)


func process(ctx context.Context, c chan string) {

    fmt.Println("process: processing (select)")

    for {

        select {

        case <-ctx.Done():

            fmt.Printf("process: ctx done bye\n")

            return

        case i := <-c:

            fmt.Printf("process: received i: %v\n", i)

        }

    }

}


func handle(ctx context.Context, readChan <-chan string) {

    c := make(chan string, 1)

    wg := &sync.WaitGroup{}

    wg.Add(1)

    go func() {

        process(ctx, c)

        wg.Done()

    }()

    defer wg.Wait()


    for i := 0; ; i++ {

        select {

        case <-ctx.Done():

            fmt.Printf("handle: ctx done bye\n")

            return

        case i := <-readChan:

            fmt.Printf("handle: received: %v\n", i)

            fmt.Printf("handle: sending for processing: %v\n", i)

            // suppose huge time passes here

            // to cause the issue we want to happen

            // we want the process() to exit due to ctx

            // cancellation before send to it happens, this creates deadlock

            time.Sleep(5 * time.Second)

            // deadlock

            c <- i

        }

    }

}


func main() {

    wg := &sync.WaitGroup{}

    ctx, cancel := context.WithCancel(context.Background())

    defer cancel()


    readChan := make(chan string, 10)

    wg.Add(1)

    go func() {

        defer wg.Done()

        for i := 0; ; i++ {

            select {

            case <-ctx.Done()  

              fmt.Printf("read: ctx done bye\n")

                return

            case readChan <- fmt.Sprintf("%d", i):

                fmt.Printf("read: sent msg: %v\n", i)

            }

        }

    }


GCT1015
浏览 119回答 2
2回答

PIPIONE

分步回顾无论你怎么想,总是取消上下文。ctx, cancel := context.WithCancel(context.Background())defer cancel()不要说。启动例程后添加&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go handle(ctx, wg)不要稀疏地消耗等待组&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; handle(ctx)&nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; }()不要在具有默认大小写的通道上循环。只需从中读取并让它解锁&nbsp; &nbsp; <-sigterm&nbsp; &nbsp; fmt.Printf("SIGTERM signal received\n")主从不阻塞信号,主块在处理例程上。信令应该只做信令,即取消上下文。&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; sigterm := make(chan os.Signal, 1)&nbsp; &nbsp; &nbsp; &nbsp; signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)&nbsp; &nbsp; &nbsp; &nbsp; <-sigterm&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("SIGTERM signal received\n")&nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; }()可以检查通道写入时的上下文取消。&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: ctx done bye\n")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case c <- fmt.Sprintf("%d", i):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("handled: sent to channel: %v\n", i)&nbsp; &nbsp; &nbsp; &nbsp; }不要花时间。睡眠,您无法用它来测试上下文取消。&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: ctx done bye\n")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case <-time.After(time.Second * 5):&nbsp; &nbsp; &nbsp; &nbsp; }因此,应用了各种规则的完整修订版代码为我们提供了package mainimport (&nbsp; &nbsp; "context"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "os"&nbsp; &nbsp; "os/signal"&nbsp; &nbsp; "sync"&nbsp; &nbsp; "syscall"&nbsp; &nbsp; "time")func process(ctx context.Context, c chan string) {&nbsp; &nbsp; fmt.Println("process: processing (select)")&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: ctx done bye\n")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case msg := <-c:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: got msg: %v\n", msg)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}func handle(ctx context.Context) {&nbsp; &nbsp; c := make(chan string, 3)&nbsp; &nbsp; wg := &sync.WaitGroup{}&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; process(ctx, c)&nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; }()&nbsp; &nbsp; defer wg.Wait()&nbsp; &nbsp; for i := 0; ; i++ {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: ctx done bye\n")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case <-time.After(time.Second * 5):&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("process: ctx done bye\n")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; case c <- fmt.Sprintf("%d", i):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("handled: sent to channel: %v\n", i)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}func main() {&nbsp; &nbsp; wg := &sync.WaitGroup{}&nbsp; &nbsp; ctx, cancel := context.WithCancel(context.Background())&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; handle(ctx)&nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; sigterm := make(chan os.Signal, 1)&nbsp; &nbsp; &nbsp; &nbsp; signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)&nbsp; &nbsp; &nbsp; &nbsp; <-sigterm&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("SIGTERM signal received\n")&nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; }()&nbsp; &nbsp; wg.Wait()}关于退出条件还有更多要讲的,但这取决于要求。

Smart猫小萌

如前所述&nbsp;https://stackoverflow.com/a/66708290/4106031&nbsp;此更改为我解决了问题。也感谢mh-cbon的规则!
随时随地看视频慕课网APP

相关分类

Go
我要回答