3

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?

Shudipta Sharma
  • 5,178
  • 3
  • 19
  • 33
Aziz
  • 928
  • 1
  • 10
  • 18
  • 1
    Let each goroutine fill its result into its own element in a slice. – Volker Nov 08 '18 at 12:59
  • 1
    @Mohammad_Aziz follow the first comment. It does not only tell about duplicity of your question but also refer to the answer you are seeking. If that doesn't help, then reply for help to this community if you need. – Shudipta Sharma Nov 08 '18 at 13:41
  • 1
    why don't you pass a function as an argument to db calls, and handle there? – RockOnGom Nov 08 '18 at 15:43
  • Do you care about the order of execution for your functions or do you care about the order of result processing? If it is the first, you should just do things sequentially, without channels or go routines. If it is the second, use WaitGroup to wait for all goroutines to finish, have them store results in a pre-defined slice or array and process the resulting array in whatever order you think it right. – Mad Wombat Nov 08 '18 at 17:07
  • I was not aware of `WaitGroup` and how it works. I do care about the order in which results are getting processed @MadWombat. – Aziz Nov 10 '18 at 04:02
  • @icza your answer covers more of a general idea about the execution of goroutines. I'm okay if you tag it to duplicate. – Aziz Nov 10 '18 at 04:06

2 Answers2

1

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"
}
Shudipta Sharma
  • 5,178
  • 3
  • 19
  • 33
poy
  • 10,063
  • 9
  • 49
  • 74
  • You should look at the comments in the question. Also it doesn't seem your solution is not as expected. You just have assigned the string into the corresponding var. You didn't care about concurrent goroutines finishing order. – Shudipta Sharma Nov 08 '18 at 13:46
  • The original sample code doesn't care about order either. It is 3 distinct function calls. – poy Nov 08 '18 at 14:04
  • 1
    May be you are right. – Shudipta Sharma Nov 08 '18 at 14:08
  • @poy I can assure that your answer did cover my solution. Just for curiosity, what would you suggest if I care about the finishing order? – Aziz Nov 10 '18 at 04:10
  • What do you mean? What would want to do with the order? – poy Nov 10 '18 at 04:12
0

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.

dieser_K3
  • 250
  • 3
  • 13