函数超时和 goroutine 泄漏

huangapple go评论84阅读模式
英文:

timeout on function and goroutine leak

问题

我帮你翻译一下:

我希望在名为 foo 的函数上设置一个超时。考虑以下代码:

func fooWithTimeout(d time.Duration) error {
    ch := make(chan error, 1)
    go func() {
        ch <- foo()
    }()

    select {
    case err := <-ch:
        return err
    case <-time.After(d):
        return errors.New("foo has timed out")
    }
}
  • 如果 foo 超时,那么它是否还能够向通道 ch 写入数据,或者存在阻塞或恐慌的风险吗?
  • 一旦 fooWithTimeout 退出,通道 ch 会发生什么情况?
  • 这段代码有潜在的问题吗?
  • 我应该在调用 foo 之前在 go func(){...}() 中添加 defer close(ch) 吗?
  • 在这个例子中,使用缓冲(大小为1)通道和非缓冲通道有关系吗?
英文:

I wish to put a timeout on a function called foo. Consider the following

func fooWithTimeout(d time.Duration) error {
    ch := make(chan error, 1)
	go func() {
		ch &lt;- foo()
	}()

	select {
	case err := &lt;-ch:
		return err
	case &lt;-time.After(d):
		return errors.New(&quot;foo has timed out&quot;)
	}
}
  • If foo has timed out, then will foo ever be able to write to channel ch or is there a risk the goroutine blocks or panics?
  • What happens to channel ch once fooWithTimeout has exited?
  • Is this code potentially problematic?
  • Should I add defer close(ch) within go func(){...}() just before calling foo?
  • Does it matter that I use a buffered (with size 1) or an unbuffered channel in this example?

答案1

得分: 1

在计时器滴答后,fooWithTimeout将返回。goroutine将继续运行,直到foo返回。

如果foo超时,它将向缓冲通道ch写入数据。

如果foo返回,通道ch最终将被垃圾回收。

你不需要关闭通道。一旦它超出作用域,它将被垃圾回收。

大量调用fooWithTimeout将创建大量资源。每次调用都会创建两个goroutine。正确的超时处理方式是将foo更改为使用上下文(context)。

英文:

After the timer tick, fooWithTimeout will return. The goroutine will continue running until foo returns.

If foo times out, it will write to channel ch because it is buffered.

The channel ch will be garbage collected eventually if foo returns.

You don't need to close the channel. Once it is out of scope, it will be garbage collected.

A large burst of calls fooWithTimeout will create large amount of resources. Each call creates two goroutines. The proper way of timing this out is to change foo to use a context.

答案2

得分: 1

在https://stackoverflow.com/a/73611534/1079543的基础上,这里是带有上下文的foo函数的代码:

package main

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

func foo(ctx context.Context) (string, error) {
	ch := make(chan string, 1)

	go func() {
		fmt.Println("Sleeping...")
		time.Sleep(time.Second * 1)
		fmt.Println("Wake up...")
		ch <- "foo"
	}()

	select {
	case <-ctx.Done():
		return "", fmt.Errorf("context cancelled: %w", ctx.Err())
	case result := <-ch:
		return result, nil
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	res, err := foo(ctx)
	if err != nil {
		log.Fatalf("foo failed: %v", err)
	}

	log.Printf("res: %s", res)
}
英文:

Building on https://stackoverflow.com/a/73611534/1079543, here is foo with a context:

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;time&quot;
)

func foo(ctx context.Context) (string, error) {
	ch := make(chan string, 1)

	go func() {
		fmt.Println(&quot;Sleeping...&quot;)
		time.Sleep(time.Second * 1)
		fmt.Println(&quot;Wake up...&quot;)
		ch &lt;- &quot;foo&quot;
	}()

	select {
	case &lt;-ctx.Done():
		return &quot;&quot;, fmt.Errorf(&quot;context cancelled: %w&quot;, ctx.Err())
	case result := &lt;-ch:
		return result, nil
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	res, err := foo(ctx)
	if err != nil {
		log.Fatalf(&quot;foo failed: %v&quot;, err)
	}

	log.Printf(&quot;res: %s&quot;, res)
}

huangapple
  • 本文由 发表于 2022年9月5日 23:03:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/73611358.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定