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.