在GO lang中使用Select时出现死锁

t3psigkw  于 2023-11-14  发布在  Go
关注(0)|答案(2)|浏览(86)

我对下面的GO程序有疑问,该程序使用go例程和select语句计算字母、数字、特殊字符和空格的个数

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
}

字符串
运行上面的代码后,我得到了下面的输出,

Spaces : 129
Digits : 8
Others : 16
letter : 464
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()


根据我的理解,Select语句会等待任何通道都准备好数据。所以我创建了单独的通道来计算字母,数字,空格和字符的数量,并且无论哪个通道首先获得数据,都会由select语句执行。
问题是为什么会发生死锁,以及有什么可能的方法来消除它?

olqngx59

olqngx591#

我想说,在这个例子中,循环是不必要的。本质上,你试图做的是并行计算,并在最后产生一个报告,对不对?
报告输出将取决于通道通信,并且不稳定(如果您有2个通道准备接收值,则将随机选择一个)。
在这个特殊的例子中,我的建议很简单:

fmt.Printf("Letters : %d\n", <-letters)
fmt.Printf("Digits : %d\n", <-digits)
fmt.Printf("Others : %d\n", <-others)
fmt.Printf("Spaces : %d", <-spaces)

字符串
你的循环所花费的时间比你从最慢的通道所能接收到的时间稍长。假设字母是最慢的一个,那么其他的会立即继续,你会有一个稳定的输出,而惩罚只是你会在所有的字母都完成后看到报告。
然而,有一个更强有力的论点是,你的函数在计算时不应该只接收通道来产生一个值。创建一个函数的同步接口,然后让它异步化要简单得多。例如:

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)
}()


考虑到这一点,如果你想尽快得到结果,你可以简单地使用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()


话虽如此,如果你想使用循环和选择语句,那么你需要确保迭代次数与读取次数相匹配。

fykwrbwg

fykwrbwg2#

我只是想提供一种替代方法,可能会对你的工作有所帮助。它使用WaitGroup,Mutex和一个单一的通道来计算你的函数。我已经包括了注解来澄清事情(希望如此)。

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}
}

字符串

相关问题