步骤顺序时使用goroutine

I feel like the answer to my question is no but asking for certainty as I've only started playing around with Go for a few days. Should we encapsulate IO bound tasks (like http requests) into goroutines even if it was to be used in a sequential use case?

Here's my naive example. Say I have a method that makes 3 http requests but need to be executed sequentially. Is there any benefit in creating the invoke methods as goroutines? I understand the example below would actually take a performance hit.

func myMethod() {
   chan1 := make(chan int)
   chan2 := make(chan int)
   chan3 := make(chan int)

   go invoke1(chan1)

   res1 := <-chan1
   invoke2(res1, chan2)

   res2 := <-chan2
   invoke3(res2, chan3)

   // Do something with <-chan3
}

One possible reason that comes to mind is to future proof the invoke methods for when they're called in a concurrent context later on when other develops start re-using the method. Any other reasons?

There's nothing standard that would say yes or no to this question.

Although you can do it correctly this way, it is much simpler to stick to plain sequential execution.

Three reasons come to mind:

  • this is still sequential: you're waiting for every single goroutine sequentially, so this buys you nothing. Performance probably doesn't change much if it's only doing an http request, both cases will spend most of their time waiting for the response.
  • error handling is much simpler if you just get result, err := invoke; if err != nil .... rather than having to pass both results and errors through channels
  • over-generalization is a more apt word than "future proofing". If you need to call your invoke methods asynchronously in the future, then change your code in the future. It will be just as easy then to add asynchronous wrappers around your functions.

You could use a channel like buf, or use []string (slice of strings urls). You don't get any benefit from gorutines, if you need only sequential execution, because we couldn't control goroutine when it's start.

But we can ask then don't wait runtime.Gosched()

From documentation: Gosched yields the processor, allowing other goroutines to run. It does not suspend the current goroutine, so execution resumes automatically.

Example of sequential execution:

package main

func main() {
    urls := []string{
        "https://google.com",
        "https://yahoo.com",
        "https://youtube.com",
    }
    buf := make([][]byte, 0, len(urls))
    for _, v := range urls {
        buf = append(buf, sendRequest(v))
    }
}

func sendRequest(url string) []byte {
    //send
    return []byte("")
}

For the solution as such, I see no gain by solving your problem the way you are in the real world. That doesn't mean there isn't gain in your solution. If you're after practice for example synchronising go routines, then you have a plethora of possibilities experimenting and learning.

So to me this is far from a clear no, but no clear yes either. I would refrain from saying maybe, it depends on what exactly your after. Are you after actually solving a problem or learning?

I am a little bit late to the party but I think I still have something to share.

Before answering the question, I would like to dive a little into the Go Proverb, Concurrency is not parellism. It is a very common misunderstanding of goroutine and Go's language feature that when people thinking of goroutine, they thinks about the ability of being parell.

But as Rob Pike pointed out in many Go Talks, what Go and goroutine auctually provides, is concurency. Concurency is a model of better interepting the real world, a way and a structure of code, of how code interact.

So back to the question. Should goroutine be used when steps are sequential? It depends on the design. If your code compose of individual parts that talks to each other very naturally, or if some of your code preserve a state and frequently return just does not make sense, or if your code fits in any other concurency design, it is perfectly fine to use goroutine and channel and select statement. Rob Pike, again, gives a Go Talk on a lexer used by now Go's text/template where the lexer uses a goroutine but the parser, obviously, only use the lexer sequentially. He stated that by using goroutine and channel, at cost of a little performance, a better API is acheived.

But on the other hand, in your example and probably what you are thinking about (codes that may require future parellism), I agree with @Marc. Stick for blocking call, at least for now.