如何在golang中顺序处理并发请求? [重复]

This question already has an answer here:

I'm new to Golang and I found go channels very interesting. My background is from JavaScript and I want to handle concurrent request sequentially in Go, sort of like Promise.all() in JavaScript. All I want is to make some requests which run concurrently and handle the returned data the order in which I have called them.

The equivalent JavaScript code will be like as follows:

async function main() {
  // Assuming all db calls will return a promise
  const firstUserPromise = firstDbCall().then((res) => res);
  const secondUserPromise = secondDbCall().then((res) => res);
  const thridUserPromise = thridDbCall().then((res) => res);

  const [
    firstUserData,
    secondUserData,
    thirdUserData
  ] = await Promise.all([firstUserPromise, secondUserPromise, thirdUserPromise]);
}

If you are not familiar with JavaScript, in the above code I'm making three database calls which are happening concurrently (from line 3 to 5). And then I'm waiting for them to give to some responses (from line 7 to 10). What nice about this code is when all three database calls are finished, I'll get the results in the order of which I'm waiting for them. firstUserData will get response from firstUserPromise, secondUserData will get from secondUserPromise and so on.

Below is a hypothetical code which I'm wanting to be equivalent of the above JavaScript code:

package main

import "fmt"

func main() {
  set = make(chan, string)
  get = make(chan, string)

  // First DB call
  go firstDbCall()
  // Second DB call
  go secondDbCall()
  // Third DB call
  go thirdDbCall()

  // How to make sending data to channels predictable
  // First data to `set` channel will send data to firstDbCall
  // Second one will `set` to secondDbCall and so on.
  set <- "userId 1"
  set <- "userId 2"
  set <- "userId 3"

  // Similarly, How to make receiving data from channels predictable
  // firstUserData will data of "userId 1", secondUserData will have
  // data of "userId 2" and so on.
  firstUserData := <-get
  secondUserData := <-get
  thirdUserData := <-get
}

Because of getting data from channels are unpredictable how can I make them predictable like the JavaScript code?

</div>

Go channels are really just thread safe queues. In this case, it doesn't look like a queue (and therefore a channel) fit your use case. I would recommend looking at sync.WaitGroup.

package main

import "sync"

func main() {
    var (
        firstUserData, secondUserData, thirdUserData string
        wg                                           sync.WaitGroup
    )
    wg.Add(3)

    // First DB call
    go func() {
        defer wg.Done()
        firstUserData = firstDbCall()
    }()

    // Second DB call
    go func() {
        defer wg.Done()
        secondUserData = secondDbCall()
    }()

    // Third DB call
    go func() {
        defer wg.Done()
        thirdUserData = thirdDbCall()
    }()

    wg.Wait()

    println(firstUserData, secondUserData, thirdUserData)
}

func firstDbCall() string {
    return "UserId1"
}

func secondDbCall() string {
    return "UserId2"
}

func thirdDbCall() string {
    return "UserId3"
}

You want to use go routines and channels for this. If the order matters, you can use a slice of a defined size like this:

package main

import "fmt"

func firstDBCall(resultSlice *[]string, doneChannel chan bool) {
    (*resultSlice)[0] = "1"
    doneChannel <- true
}

func secondDBCall(resultSlice *[]string, doneChannel chan bool) {
    (*resultSlice)[1] = "2"
    doneChannel <- true 
}

func thirdDBCall(resultSlice *[]string, doneChannel chan bool) {
    (*resultSlice)[2] = "3"
    doneChannel <- true
}

func main() {
    resultSlice := make([]string, 3)
    doneChannel := make(chan bool)
    go firstDBCall(&resultSlice, doneChannel)
    go secondDBCall(&resultSlice, doneChannel)
    go thirdDBCall(&resultSlice, doneChannel)

    for i := 0; i < 3; i++ {
        <-doneChannel
    }
    fmt.Println(resultSlice)
}

This code calls three functions concurrently with the go keyword. Where the slice is filled, you would make your database query first, before filling the result into the slice. After calling the go routines, we have a loop which just checks by using a channel if all routines are finished. This works because reading from a channel is blocking.

Another, maybe more elegant solution would be to use a struct instead of a slice. This would allow for different types of the queried values, too.