Reputation: 4702
I am taking a look at the Google Go
language as I am building a realtime system, and I find the sharing of resources through channels a bit confusing. I'm trying for the sake of understanding, to let to different goroutines
to increment and decrement a shared value the same number of times, ending up at 0. I do know my code is wrong, but I'm not really getting the hang of it. Anybody care to explain what's wrong here?
package main
import (
. "fmt"
. "runtime"
)
func increment(c chan int) {
for x := 0; x < 10; x++ {
a := <-c
a++
c <- a
}
}
func decrement(c chan int) {
for x := 0; x < 10; x++ {
a := <-c
a--
c <- a
}
}
func main() {
GOMAXPROCS(NumCPU())
c := make(chan int)
go increment(c)
go decrement(c)
Println(<-c)
}
I could use a mutex or a semaphore similar to what I would do using C
or Python
, although I want to take advantage of the channels in Go
.
**UPDATE
Would adding a WaitGroup
change the program flow? I added a WaitGroup
, and it worked well. Although, I added the Done()
function after the whole for loop, will then the whole increment
run before decrement
? I kind of want them to go 'in parallel' as far as it can, I know that only one routine can access I, but I want them to run independent of each other.
Upvotes: 0
Views: 1427
Reputation: 54081
Here is a complete example of the kind of shared state engine that I think you are talking about
Note use of WaitGroup
as you suggested in your edit to synchronise the two channels.
PS don't use import . "fmt"
it is considered to be bad practice.
package main
import (
"fmt"
"runtime"
"sync"
)
// Commands for the engine
const (
INC = iota
DEC
ANSWER
QUIT
)
// Engine which takes commands and acts on some shared state
func engine(c chan int, reply chan int) {
counter := 0
for {
switch <-c {
case INC:
counter++
case DEC:
counter--
case ANSWER:
reply <- counter
case QUIT:
reply <- counter
return
}
}
}
// Add n times then signal done via the waitgroup
func increment(n int, c chan int, wg *sync.WaitGroup) {
defer wg.Done()
for x := 0; x < n; x++ {
c <- INC
}
}
// Subtract n times then signal done
func decrement(n int, c chan int, wg *sync.WaitGroup) {
defer wg.Done()
for x := 0; x < n; x++ {
c <- DEC
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
// Start the engine
c := make(chan int)
reply := make(chan int)
go engine(c, reply)
// Do adding and subtracting and wait for them to finish
wg := new(sync.WaitGroup)
wg.Add(2)
go increment(101, c, wg)
go decrement(100, c, wg)
wg.Wait()
// Read the answer
c <- ANSWER
fmt.Printf("Total is %d\n", <-reply)
// Stop the engine
c <- QUIT
<-reply
fmt.Printf("All done\n")
}
Upvotes: 1
Reputation: 49167
There are a few problems with your code:
Both goroutines try to read from the channel at the same time. This is a deadlock as there is nothing in the channel to read.
Println(<-c)
reads one value from the channel, not a result. It might read a result if you waited for both goroutines to finish, but that requires adding a WaitGroup
. a Waitgroup is like a semaphore allowing each goroutine to decrement a counter of pending goroutines, and allowing the caller to wait for them to finish some task.
Since sending blocks if there is no reader and reading is blocking if there's no sender, and you're a. waiting for both goroutines to finish first and b. doing one more read than writes (the Println read), you need a buffered channel
, that has exactly one extra place in the buffer.
You need to push an initial value in the channel for the process to start.
I've changed your code a bit and this example now works (although notice that it's not realy increment->decrement->increment->.... but rathter increment->increment->...->decrement->decrement->.... until we're done.
package main
import (
. "fmt"
. "runtime"
"sync"
)
func increment(c chan int, wg *sync.WaitGroup) {
for x := 0; x < 10; x++ {
a := <-c
Println("increment read ", a)
a++
c <- a
}
Println("Incrment done!")
wg.Done()
}
func decrement(c chan int, wg *sync.WaitGroup) {
for x := 0; x < 10; x++ {
a := <-c
Println("Decrement read ", a)
a--
c <- a
}
Println("Dencrment done!")
wg.Done()
}
func main() {
GOMAXPROCS(NumCPU())
//we create a buffered channel with 1 extra space. This means
//you can send one extra value into it if there is no reader, allowing for the final result to be pushed to println
c := make(chan int, 1)
//we create a wait group so we can wait for both goroutines to finish before reading the result
wg := sync.WaitGroup{}
wg.Add(1) //mark one started
go increment(c, &wg)
wg.Add(1) //mark another one started. We can just do Add(2) BTW
go decrement(c, &wg)
//now we push the initial value to the channel, starting the dialog
c <- 0
//let's wait for them to finish...
wg.Wait()
//now we have the result in the channel's buffer
Println("Total: ", <-c )
}
Upvotes: 2