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

问题描述 投票:0回答:2

我对下面的 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 语句执行。

问题是为什么会发生死锁以及有什么可能的方法来消除它?

go concurrency goroutine
2个回答
1
投票

我想说在这个例子中循环是不必要的。本质上,您想要做的就是并行计算并最终生成报告,对吗?

报告输出取决于通道通信,不稳定(如果您有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)
}()

考虑到这一点,如果您希望尽快得到结果,您可以简单地使用等待组:

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

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


0
投票

我只是提供一种替代方法,在您解决此问题时可能会派上用场。它使用 WaitGroup、互斥体和单个通道来对函数进行计数。

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?`

    countStream := make(chan *charCount)
    countWait := &sync.WaitGroup{}
    countWait.Add(4)

    tot := 0
    totLock := sync.Mutex{}

    go cntLetters(text, countStream)
    go cntDigits(text, countStream)
    go cntOthers(text, countStream)
    go cntSpaces(text, countStream)

    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)
            totLock.Lock()
            tot += cc.count
            totLock.Unlock()
            countWait.Done()
        }
    }()
    countWait.Wait()
    close(countStream)
    totLock.Lock()
    defer totLock.Unlock()
    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}
}
© www.soinside.com 2019 - 2024. All rights reserved.