Reputation: 107
I have a doubt regarding the below GO program which calculates the no of letters, digits, special characters and spaces using go routines and select statement
func main() {
letters := make(chan int)
digits := make(chan int)
others := make(chan int)
spaces := make(chan int)
//c := make(chan int)
//tot := 0
text := `William Butler Yeats (1865-1939) is one of the greatest of all Irish poets.
No Second Troy
Why should I blame her that she filled my days
With misery, or that she would of late
Have taught to ignorant men most violent ways,
Or hurled the little streets upon the great,
Had they but courage equal to desire?
What could have made her peaceful with a mind
That nobleness made simple as a fire,
With beauty like a tightened bow, a kind
That is not natural in an age like this,
Being high and solitary and most stern?
Why, what could she have done, being what she is?
Was there another Troy for her to burn?`
go cntLetters(text, letters)
go cntDigits(text, digits)
go cntOthers(text, others)
go cntSpaces(text, spaces)
for i := 0; i < 5; i++ {
select {
case t1 := <-letters:
fmt.Println("letter :", t1)
case t2 := <-digits:
fmt.Println("Digits :", t2)
case t3 := <-others:
fmt.Println("Others :", t3)
case t4 := <-spaces:
fmt.Println("Spaces :", t4)
}
}
//fmt.Println(tot)
}
func cntLetters(txt string, letters chan int) {
s := 0
for _, v := range txt {
if unicode.IsLetter(v) {
s++
}
}
letters <- s
}
func cntDigits(txt string, digits chan int) {
s := 0
for _, v := range txt {
if unicode.IsDigit(v) {
s++
}
}
digits <- s
}
func cntOthers(txt string, others chan int) {
s := 0
for _, v := range txt {
if !unicode.IsDigit(v) && !unicode.IsLetter(v) && !unicode.IsSpace(v) {
s++
}
}
others <- s
}
func cntSpaces(txt string, spaces chan int) {
s := 0
for _, v := range txt {
if unicode.IsSpace(v) {
s++
}
}
spaces <- s
}
After running the above code , I am getting the below output,
Spaces : 129
Digits : 8
Others : 16
letter : 464
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select]:
main.main()
According to my understanding Select statement waits till any of the channels are ready with data. So I have created separate channels to calculate the no of letters, digits, spaces and characters and whichever channel gets the data first is executed by the select statement.
The query is why the deadlock is happening and any possible ways to remove it?
Upvotes: 1
Views: 67
Reputation: 89
I'm just going to provide an alternate approach that might be of use as you work through this. It uses a WaitGroup, a Mutex, and a single channel for counts from your functions. I've included comments to clarify things (hopefully).
package main
import (
"fmt"
"sync"
"unicode"
)
type charKind int
type charCount struct {
kind charKind
count int
}
func newCharCount(kind charKind, count int) (*charCount) {
return &charCount{kind: kind, count: count}
}
const (
OTHER charKind = iota
LETTER
DIGIT
SPACE
)
func main() {
text := `William Butler Yeats (1865-1939) is one of the greatest of all Irish poets.
No Second Troy
Why should I blame her that she filled my days
With misery, or that she would of late
Have taught to ignorant men most violent ways,
Or hurled the little streets upon the great,
Had they but courage equal to desire?
What could have made her peaceful with a mind
That nobleness made simple as a fire,
With beauty like a tightened bow, a kind
That is not natural in an age like this,
Being high and solitary and most stern?
Why, what could she have done, being what she is?
Was there another Troy for her to burn?`
// make a "done" channel to signal to main
// goroutine when the range loop is finished
done := make(chan struct{})
// make a channel for the output of
// count functions, using the type
// above that includes what kind and
// how many
countStream := make(chan *charCount)
// make a waitgroup and add 4, the
// number of functions you want to
// wait for (4)
countWait := &sync.WaitGroup{}
countWait.Add(4)
// initialize a variable for the
// total, scoped outside the go func
// below, and create a mutex to make
// more concurrency-safe
tot := 0
totLock := sync.Mutex{}
// spawn your count functions,
// passing the single channel
go cntLetters(text, countStream)
go cntDigits(text, countStream)
go cntOthers(text, countStream)
go cntSpaces(text, countStream)
// spawn a goroutine with a range
// loop over the channel -- this
// will exit when we close the count
// channel
go func() {
for cc := range countStream {
label := "?"
switch cc.kind {
case LETTER:
label = "letters"
case DIGIT:
label = "digits"
case OTHER:
label = "others"
case SPACE:
label = "spaces"
}
fmt.Printf("%s: %d\n", label, cc.count)
// lock, then increment the total, unlock
totLock.Lock()
tot += cc.count
totLock.Unlock()
// decrement the waitgroup
countWait.Done()
}
done <- struct{}{}
}()
// wait for the waitgroup
countWait.Wait()
// close the channel, make sure
// the range loop exits in the above
// goroutine
close(countStream)
// print the total -- the lock/unlock
// isn't strictly necessary, but a good
// habit
totLock.Lock()
defer totLock.Unlock()
// wait for range loop goroutine to exit
<-done
fmt.Printf("total: %d\n", tot)
}
func cntLetters(txt string, countStream chan<- *charCount) {
s := 0
for _, v := range txt {
if unicode.IsLetter(v) {
s++
}
}
countStream <- &charCount{kind: LETTER, count: s}
}
func cntDigits(txt string, countStream chan<- *charCount) {
s := 0
for _, v := range txt {
if unicode.IsDigit(v) {
s++
}
}
countStream <- &charCount{kind: DIGIT, count: s}
}
func cntOthers(txt string, countStream chan<- *charCount) {
s := 0
for _, v := range txt {
if !unicode.IsDigit(v) && !unicode.IsLetter(v) && !unicode.IsSpace(v) {
s++
}
}
countStream <- &charCount{kind: OTHER, count: s}
}
func cntSpaces(txt string, countStream chan<- *charCount) {
s := 0
for _, v := range txt {
if unicode.IsSpace(v) {
s++
}
}
countStream <- &charCount{kind: SPACE, count: s}
}
Upvotes: 0
Reputation:
I would say that the loop is not necessary in this example. Essentially what you are trying to do is to parallelize calculations and at the end produce a report, right?
The report output would depend on the channel communication and is not stable (if you have 2 channels ready to receive value, the one picked would be randomly selected).
In this particular example, what I would suggest is simply this:
fmt.Printf("Letters : %d\n", <-letters)
fmt.Printf("Digits : %d\n", <-digits)
fmt.Printf("Others : %d\n", <-others)
fmt.Printf("Spaces : %d", <-spaces)
The time that your loop will spend is slightly greater than the time you can receive from the slowest channel. Let's say letters are the slowest one, then others will proceed immediately, and you will have a stable output and the penalty is only that you will see the report once all of them are done.
However, there is a much stronger argument that your functions doing calculations should not receive channel only to produce a value. It is much simpler to create a synchronous interface of a function and make it asynchronous later. For example:
func cntDigits(txt string) int {
s := 0
for _, v := range txt {
if unicode.IsDigit(v) {
s++
}
}
return s
}
// caller making it asynchronous
go func() {
digits <- cntDigits(text)
}()
With this in mind, if you want results as quickly as you can, you can simply use a waitgroup:
var wg sync.WaitGroup
wg.Add(1) // has to be before you spin up goroutine
go func() {
fmt.Println("Digits: ", cntDigits(text))
wg.Done()
}()
wg.Wait()
Having said that, if you want to use a loop and select statement, then you need to make sure that number of iterations matches the number of reads.
Upvotes: 1