英文:
When should I use concurrency in Go?
问题
除了处理多个服务器请求之外,还有其他时候需要并发吗?我问这个问题是因为并发在语言中已经被内置了,如果不使用它,我感觉很浪费,但我几乎找不到使用它的场景。
英文:
So besides handling multiple server requests is there any other time that concurrency is relevant? I ask because it's so built into the language that I feel wasteful if I don't use it but I can barely find a use for it.
答案1
得分: 9
我现在是你的中文翻译助手,以下是翻译好的内容:
不是Go
的专家(尚未),但我可以说:
只要最容易的方式。
Go
中并发模型的美妙之处在于,它不是基于多核架构的检查和平衡,通常会出现问题,而是一种多线程范式,不仅适用于多核架构,也适用于分布式系统架构。
你不需要为多个goroutine
做特殊安排,它们可以和谐地一起工作 - 它们只是这样做!
这是一个自然并发算法的示例 - 我想将多个通道合并为一个。一旦所有输入通道都耗尽,我就想关闭输出通道。
使用并发只是更简单 - 实际上它看起来甚至不像并发 - 它几乎看起来像是过程化的。
/*
将多个通道复用为一个通道。
*/
func Mux(channels []chan big.Int) chan big.Int {
// 当每个通道关闭时进行倒计时。当计数为零时 - 关闭 ch。
var wg sync.WaitGroup
wg.Add(len(channels))
// 输出通道。
ch := make(chan big.Int, len(channels))
// 为每个通道创建一个 goroutine。
for _, c := range channels {
go func(c <-chan big.Int) {
// 进行数据传输。
for x := range c {
ch <- x
}
// 它已关闭。
wg.Done()
}(c)
}
// 在传输完成后关闭通道。
go func() {
// 等待所有人完成。
wg.Wait()
// 关闭通道。
close(ch)
}()
return ch
}
在这里,我唯一需要做出的并发让步是使用sync.WaitGroup
作为并发计数器。
请注意,这不是完全我自己的工作 - 我在这里得到了很多帮助这里。
英文:
Not an expert in Go
(yet) but I'd say:
Whenever it is easiest to do so.
The beauty of the concurrency model in Go
is that it is not fundamentally a multi-core architecture with checks and balances where things usually break - it is a multi-threaded paradigm that not only fits well into a multi-core architecture, it also fits well into a distributed system architecture.
You do not have to make special arrangements for multiple goroutines
to work together harmoniously - they just do!
Here's an example of a naturally concurrent algorithm - I want to merge multiple channels into one. Once all of the input channels are exhausted I want to close the output channel.
It is just simpler to use concurrency - in fact it doesn't even look like concurrency - it looks almost procedural.
/*
Multiplex a number of channels into one.
*/
func Mux(channels []chan big.Int) chan big.Int {
// Count down as each channel closes. When hits zero - close ch.
var wg sync.WaitGroup
wg.Add(len(channels))
// The channel to output to.
ch := make(chan big.Int, len(channels))
// Make one go per channel.
for _, c := range channels {
go func(c <-chan big.Int) {
// Pump it.
for x := range c {
ch <- x
}
// It closed.
wg.Done()
}(c)
}
// Close the channel when the pumping is finished.
go func() {
// Wait for everyone to be done.
wg.Wait()
// Close.
close(ch)
}()
return ch
}
The only concession I have to make to concurrency here is to use a sync.WaitGroup
as a counter for concurrent counting.
Note that this is not purely my own work - I had a great deal of help with this here.
答案2
得分: 7
这里有一个来自Go语言发明者之一Rob Pike的很好的例子,展示了使用并发的好处,因为它是表达问题解决方案的一种更简单的方式:
稍微概括一下,任何生产者-消费者问题都很适合使用两个goroutine通过通道传递生产者的输出给消费者。
并发的另一个很好的用途是与多个输入/输出源(磁盘、网络、终端等)进行交互。你的程序应该能够在任何这些源中产生结果时唤醒并进行一些工作。可以使用一个线程和像poll(2)或select(2)这样的系统调用来实现这一点。当线程唤醒时,它必须找出哪个结果到达了,找到相关任务中它离开的位置,并从那里继续执行。这需要编写很多代码。
使用每个任务一个goroutine来编写这些代码要简单得多。然后,该任务的状态会隐式地在goroutine中被捕获,从离开的地方继续执行就像唤醒并运行一样简单。
英文:
Here is a good example from one of Go's inventors, Rob Pike, of using concurrency because it is an easier way to express the solution to a problem:
Generalizing on that a bit, any producer-consumer problem is a natural fit for 2 goroutines using a channel to pass outputs from the producer to the consumer.
Another good use for concurrency is interacting with multiple input/output sources (disks, network, terminal, etc.). Your program should be able to wake up and do some work whenever a result comes from any of these sources. It is possible to do this with one thread and a system call like poll(2) or select(2). When your thread wakes up, it must figure out which result came in, find where it left off in the relevant task, and pick up from there. That's a lot of code you need to write.
Writing that code is much easier using one goroutine per task. Then the state of that task is captured implicitly in the goroutine, and picking up where it left off is as simple as waking up and running.
答案3
得分: 5
我的两分钱...如果你只在并发的上下文中考虑通道/ goroutine,那你就错过了很多。
虽然Go不是一种面向对象的语言,也不是严格的函数式语言,但它允许你借鉴两者的设计特点并应用它们。
面向对象设计的一个基本原则是单一职责原则。应用这个原则会迫使你从消息的角度思考设计,而不是复杂的对象行为。在Go中,你可以使用相同的设计约束,通过连接单一目的的函数的“通道上的消息”来思考。
这只是一个例子,但如果你开始以这种方式思考,你会发现更多的例子。
英文:
My 2 cents... If you think about channels/goroutines only in the context of concurrency, you are missing the boat.
While go is not an object language or strictly a functional language, it does allow you to take design features from both and apply them.
One of the basic tenets of object oriented design is the Single Responsibility
Principle. Applying this principle forces you to think about design in terms of messages, rather than complex object behavior. These same design constraints can be used in go, to allow you to start thinking about "messages on channels" connecting single purpose functions.
This is just one example, but if you start thinking this way, you'll see many more.
答案4
得分: 0
我不是Go的专家,所以我的一些方法可能不是规范的,但以下是我迄今为止发现并发有用的一些方式:
- 在等待网络请求、磁盘I/O或数据库查询完成时执行操作
- 更快地执行分而治之的算法
- 由于goroutine是函数,而函数在Go中是一等公民,你可以将它们作为变量传递。当程序有许多自治的部分时,这非常方便。(例如,我正在尝试模拟城市的交通系统。每辆车都是一个独立的goroutine,它们通过使用通道与交叉口和其他车辆进行通信。每个goroutine都在做自己的事情。)
- 在不同设备上同时进行I/O操作
- 使用并发在图像的一组点上执行Dijkstra算法以绘制“智能剪刀”线条——每个点一个goroutine,这种实现方式显著提高了速度。
- GoConvey使用并发同时在多个包中运行测试,以便在使用Web UI进行调试时更快地响应变化。(作为固有的奖励,这样做会为测试序列添加一些伪随机性,使测试结果真正一致。)
并发在以下情况下可能有用(注意:并不总是如此):当你有一些可以独立运行但在其他goroutine的某些点上依赖数据或某种信号的操作时,你可以使用通道进行通信。
关于这些问题的一些启发和重要区别,以及一些有趣的Gopher图片,请参阅Concurrency is not Parallelism。
英文:
Also not an expert at Go, so some of my approaches may be non-canonical, but here are some ways I've found concurrency useful so far:
- Doing operations while waiting for a network request, disk I/O, or database query to finish
- Executing divide-and-conquer algorithms more quickly
- Since goroutines are functions, and functions are first-class citizens in Go, you can pass them around as variables. This is convenient when your program has many autonomous pieces. (For example, I'm playing around with simulating a city's traffic system. Each vehicle is its own goroutine, and they communicate with intersections and other vehicles by using channels. Each one does its own thing.)
- Simultaneous I/O operations across different devices
- Used concurrency to perform Dijkstra's algorithm on a set of points in an image to draw "intelligent scissor" lines -- one goroutine per point made this implementation significantly faster.
- GoConvey uses concurrency to run tests across packages at the same time to respond faster to changes while debugging using the web UI. (As an inherent bonus, this adds some pseudo-randomness to the testing sequence so your test results are truly consistent.)
Concurrency could be (read: "is sometimes, but not necessarily always") useful when you have operations that could run independently of each other but would otherwise run sequentially. Even if those operations depend on data or some sort of signal from other goroutines at certain points, you can communicate that all with channels.
For some inspiration and an important distinction in these matters, and for some funny gopher pictures, see Concurrency is not Parallelism.
答案5
得分: -1
任何时候,一个数据项不依赖于前一个数据项。
英文:
Any time an item of data doesn't depend on the previous one.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论