英文:
How to go about testing go routines?
问题
这个问题的一个例子是当用户创建一个资源并删除一个资源时。我们将执行操作,并增加(减少)一个计数器缓存。
在测试中,有时会出现竞争条件,计数器缓存尚未被go例程更新。
编辑:对于混淆表示歉意,澄清一下:计数器缓存不在内存中,实际上是数据库中的一个字段。竞争条件不是针对内存中的变量,而是go例程可能在写入数据库本身时速度较慢!
目前,我在操作后使用1秒的延迟,以确保在测试计数器缓存之前已经更新了计数器缓存。有没有另一种方法来测试go例程,而不需要任意的1秒延迟等待go例程完成?
谢谢。
英文:
An example of this problem is when a user creates a resource and deletes a resource. We will perform the operation and also increment (decrement) a counter cache.
In testing, there is sometimes a race condition where the counter cache has not been updated by the go routine.
EDIT: Sorry about the confusion, to clarify: the counter cache is not in memory, it is actually a field in the database. The race condition is not to a variable in memory, it is actually that the goroutine might be slow to write into the database itself!
I currently use a 1 second sleep after the operation to ensure that the counter cache has been updated before testing the counter cache. Is there another way to test go routine without the arbitrary 1 second sleep to wait for the go routine to finish?
Cheers
答案1
得分: 3
在测试中,有时会出现竞态条件,即计数器缓存尚未被go协程更新。我目前在操作后使用1秒的休眠来确保计数器缓存在测试计数器缓存之前已经被更新。
哎呀,我不想这么说,但你的做法是错误的。Go语言有一流的特性可以使并发变得简单!如果你正确使用它们,就不可能出现竞态条件。
事实上,有一个工具可以帮助你检测竞态条件。我敢打赌它会对你的程序提出警告。
一个简单的解决方案是:
- 主协程创建一个用于跟踪计数器的go协程。
- 该go协程只需执行一个select语句,接收消息来增加/减少或读取计数器。(如果是读取操作,它将通过一个通道返回计数器的值)
- 当你创建/删除资源时,通过通道向go协程计数器发送相应的消息。
- 当你想要读取计数器时,发送一个读取消息,然后读取返回通道的值。
(另一种选择是使用锁。这可能会稍微提高性能,但编写和确保正确性会更加繁琐。)
英文:
> In testing, there is sometimes a race condition where the counter cache has not been updated by the go routine. I currently use a 1 second sleep after the operation to ensure that the counter cache has been updated before testing the counter cache.
Yikes, I hate to say it, but you're doing it wrong. Go has first-class features to make concurrency easy! If you use them correctly, it's impossible to have race conditions.
In fact, there's a tool that will detect races for you. I'll bet it complains about your program.
One simple solution:
- Have the main routine create a goroutine for keeping track of the counter.
- the goroutine will just do a select and get a message to increment/decrement or read the counter. (If reading, it will be passed in a channel to return the number)
- when you create/delete resources, send an appropriate message to the goroutine counter via it's channel.
- when you want to read the counter, send a message for read, and then read the return channel.
(Another alternative would be to use locks. It would be a tiny bit more performant, but much more cumbersome to write and ensure it's correct.)
答案2
得分: 0
一种解决方案是让你的计数器提供一个通道,该通道在值发生变化时立即更新。在Go语言中,通过通信来实现同步是常见的做法。例如,你的Counter
可以像这样:
type Counter struct {
value int
ValueChange chan int
}
func (c *Counter) Change(n int) {
c.value += n
c.ValueChange <- c.value
}
每当调用Change
方法时,新值都会通过通道传递,等待该值的任何人都会解除阻塞并继续执行,从而与计数器同步。使用这段代码,你可以像这样监听ValueChange
通道的变化:
v := <-c.ValueChange
同时调用c.Change
不再是一个问题。
你可以在playground上运行示例代码。
英文:
One solution is to make to let your counter offer a channel which is updated as soon as the value
changes. In go it is common practice to synchronize by communicating the result. For example your
Couter
could look like this:
type Counter struct {
value int
ValueChange chan int
}
func (c *Counter) Change(n int) {
c.value += n
c.ValueChange <- c.value
}
Whenever Change
is called, the new value is passed through the channel and whoever is
waiting for the value unblocks and continues execution, therefore synchronizing with the
counter. With this code you can listen on ValueChange
for changes like this:
v := <-c.ValueChange
Concurrently calling c.Change
is no problem anymore.
There is a runnable example on play.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论