GolangBlog | Go Concurrency Patterns: Timing out, moving on

1. 版权

按https://golang.org/doc/copyright.html, 原文内容使用 Creative Commons Attribution 3.0 License, 代码使用 BSD license.

使用原文之外的部分, 需注明出处: https://blog.csdn.net/big_cheng/article/details/108024703.

2. 原文

Go Concurrency Patterns: Timing out, moving on
Andrew Gerrand
23 September 2010

内容

Concurrent programming has its own idioms. A good example is timeouts. Although Go’s channels do not support them directly, they are easy to implement. Say we want to receive from the channel ch, but want to wait at most one second for the value to arrive. We would start by creating a signalling channel and launching a goroutine that sleeps before sending on the channel:

timeout := make(chan bool, 1)
go func() {
    time.Sleep(1 * time.Second)
    timeout <- true
}()

We can then use a select statement to receive from either ch or timeout. If nothing arrives on ch after one second, the timeout case is selected and the attempt to read from ch is abandoned.

select {
case <-ch:
    // a read from ch has occurred
case <-timeout:
    // the read from ch has timed out
}

The timeout channel is buffered with space for 1 value, allowing the timeout goroutine to send to the channel and then exit. The goroutine doesn’t know (or care) whether the value is received. This means the goroutine won’t hang around forever if the ch receive happens before the timeout is reached. The timeout channel will eventually be deallocated by the garbage collector.

(In this example we used time.Sleep to demonstrate the mechanics of goroutines and channels. In real programs you should use time.After, a function that returns a channel and sends on that channel after the specified duration.)

Let’s look at another variation of this pattern. In this example we have a program that reads from multiple replicated databases simultaneously. The program needs only one of the answers, and it should accept the answer that arrives first.

The function Query takes a slice of database connections and a query string. It queries each of the databases in parallel and returns the first response it receives:

func Query(conns []Conn, query string) Result {
    ch := make(chan Result)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <-ch
}

In this example, the closure does a non-blocking send, which it achieves by using the send operation in select statement with a default case. If the send cannot go through immediately the default case will be selected. Making the send non-blocking guarantees that none of the goroutines launched in the loop will hang around. However, if the result arrives before the main function has made it to the receive, the send could fail since no one is ready.

This problem is a textbook example of what is known as a race condition, but the fix is trivial. We just make sure to buffer the channel ch (by adding the buffer length as the second argument to make), guaranteeing that the first send has a place to put the value. This ensures the send will always succeed, and the first value to arrive will be retrieved regardless of the order of execution.

These two examples demonstrate the simplicity with which Go can express complex interactions between goroutines.

3. 笔记

要从ch 读一个数, 但最多只能等1秒, 可以:

timeout := make(chan bool, 1)
go func() {
	time.Sleep(1 * time.Second)
	timeout <- true
}()

select {
case <-ch:
	// read from ch
case <-timeout:
	// timed out
}

如上select 的2个case 谁先能执行(ch有数了、timeout有数了) 就执行哪个, 这样保证主线程最多读等1秒. 注意由于timeout 的buffer=1, 上面go func() 在1秒后(无论ch读了没有) 总是可以timeout <- true, 从而正常结束.

另一个例子: 给定一组连接, 同时执行一个查询, 返回最早得到的查询结果:

func Query(conns []Conn, query string) Result {
	ch := make(chan Result)
	for _, conn := range conns {
		go func(c Conn) {
			select {
			case ch <- c.DoQuery(query):
			default:
			}
		}(conn)
	}
	return <-ch
}

上面由于ch 没有buffer, 只要有一个goroutine 先查询完并ch <- 结果, 其他goroutine (在主线程 <-ch 前) 均会选择执行select 的default.
但问题是存在一种可能: 在主线程 <-ch 前, 所有goroutine 都查询完了, 由于ch 尚未ready, 所以全部选择执行select 的default. 此时主线程将永远停在 <-ch 那里.
此问题是典型的race condition 问题 (程序执行结果随环境/事件顺序而变, 具有不确定性).
解决方法是将ch 改为buffer=1, 这样至少有一个goroutine 会发出结果, 而主线程总是可以取到该结果 (select 的default 确保所有goroutine 都可以结束).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值