亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

程序使用等待組進入死鎖

程序使用等待組進入死鎖

Go
繁星coding 2022-07-18 16:56:33
我正在編寫一個程序,它讀取名為 orders.csv 的文件中的訂單號列表,并將其與文件夾中存在的其他 csv 文件進行比較。問題是即使使用等待組它也會陷入死鎖,我不知道為什么。出于某種原因,stackoverflow 說我的帖子主要是代碼,所以我必須添加這一行,因為如果有人想幫助我調試我遇到的這個問題,整個代碼都是必要的。package mainimport (    "bufio"    "fmt"    "log"    "os"    "path/filepath"    "strings"    "sync")type Files struct {    filenames []string}type Orders struct {    ID []string}var ordersFilename string = "orders.csv"func main() {    var (        ordersFile *os.File        files       Files        orders     Orders        err        error    )    mu := new(sync.Mutex)    wg := &sync.WaitGroup{}    wg.Add(1)    if ordersFile, err = os.Open(ordersFilename); err != nil {        log.Fatalln("Could not open file: " + ordersFilename)    }    orders = getOrderIDs(ordersFile)    files.filenames = getCSVsFromCurrentDir()    var filenamesSize = len(files.filenames)    var ch = make(chan map[string][]string, filenamesSize)    var done = make(chan bool)    for i, filename := range files.filenames {        go func(currentFilename string, ch chan<- map[string][]string, i int, orders Orders, wg *sync.WaitGroup, filenamesSize *int, mu *sync.Mutex, done chan<- bool) {            wg.Add(1)            defer wg.Done()            checkFile(currentFilename, orders, ch)            mu.Lock()            *filenamesSize--            mu.Unlock()            if i == *filenamesSize {                done <- true                close(done)            }        }(filename, ch, i, orders, wg, &filenamesSize, mu, done)    }    select {    case str := <-ch:        fmt.Printf("%+v\n", str)    case <-done:        wg.Done()        break    }    wg.Wait()    close(ch)}
查看完整描述

3 回答

?
夢里花落0921

TA貢獻1772條經驗 獲得超6個贊

  1. 第一個問題是wg.Addalways 必須在它所代表的 goroutine(s) 之外。如果不是,則 wg.Wait可能會在 goutine 實際開始運行(并被調用wg.Add)之前調用該調用,因此會“認為”沒有什么可等待的。

  2. 代碼的第二個問題是它等待例程完成的方式有多種。有渠道WaitGroup,有done渠道。僅使用其中之一。哪一個還取決于如何使用 goroutines 的結果。在這里,我們來到下一個問題。

  3. 第三個問題是收集結果。目前,該代碼僅打印/使用來自 goroutine 的單個結果。for { ... }在選擇周圍放置一個循環,如果通道關閉,則使用它return來跳出循環。done(請注意,您不需要在done頻道上發送任何內容,關閉它就足夠了。)

改進版 0.0.1

所以這里的第一個版本(包括其他一些“代碼清理”)帶有done用于關閉和WaitGroup刪除的通道:

func main() {

    ordersFile, err := os.Open(ordersFilename)

    if err != nil {

        log.Fatalln("Could not open file: " + ordersFilename)

    }


    orders := getOrderIDs(ordersFile)


    files := Files{

        filenames: getCSVsFromCurrentDir(),

    }


    var (

        mu = new(sync.Mutex)

        filenamesSize = len(files.filenames)

        ch = make(chan map[string][]string, filenamesSize)

        done = make(chan bool)

    )


    for i, filename := range files.filenames {

        go func(currentFilename string, ch chan<- map[string][]string, i int, orders Orders, filenamesSize *int, mu *sync.Mutex, done chan<- bool) {

            checkFile(currentFilename, orders, ch)

            mu.Lock()

            *filenamesSize--

            mu.Unlock()

            // TODO: This also accesses filenamesSize, so it also needs to be protected with the mutex:

            if i == *filenamesSize {

                done <- true

                close(done)

            }

        }(filename, ch, i, orders, &filenamesSize, mu, done)

    }


    // Note: closing a channel is not really needed, so you can omit this:

    defer close(ch)

    for {

        select {

        case str := <-ch:

            fmt.Printf("%+v\n", str)

        case <-done:

            return

        }

    }

}

改進版 0.0.2

但是,在您的情況下,我們有一些優勢。我們確切地知道我們啟動了多少個 goroutine,因此也知道我們期望有多少結果。(當然,如果每個 goroutine 返回一個當前代碼所做的結果。)這為我們提供了另一種選擇,因為我們可以使用另一個具有相同迭代次數的 for 循環來收集結果:

func main() {

    ordersFile, err := os.Open(ordersFilename)

    if err != nil {

        log.Fatalln("Could not open file: " + ordersFilename)

    }


    orders := getOrderIDs(ordersFile)


    files := Files{

        filenames: getCSVsFromCurrentDir(),

    }


    var (

        // Note: a buffered channel helps speed things up. The size does not need to match the size of the items that will

        //   be passed through the channel. A fixed, small size is perfect here.

        ch = make(chan map[string][]string, 5)

    )


    for _, filename := range files.filenames {

        go func(filename string) {

            // orders and channel are not variables of the loop and can be used without copying

            checkFile(filename, orders, ch)

        }(filename)

    }


    for range files.filenames {

        str := <-ch

        fmt.Printf("%+v\n", str)

    }

}

簡單很多,不是嗎?希望有幫助!


查看完整回答
反對 回復 2022-07-18
?
慕的地8271018

TA貢獻1796條經驗 獲得超4個贊

這段代碼有很多錯誤。

  1. 您使用 WaitGroup 錯誤。Add 必須在主 goroutine 中調用,否則有可能在所有 Add 調用完成之前調用 Wait。

  2. 在初始化與 Done() 調用不匹配的 WaitGroup 之后,有一個無關的 Add(1) 調用,因此 Wait 永遠不會返回(假設上面的點是固定的)。

  3. 您同時使用 WaitGroup 和 done 通道來表示完成。這充其量是多余的。

  4. 您正在讀取 filenamesSize 而沒有持有鎖(在if i == *filenamesSize語句中)。這是一個競爭條件。

  5. 首先,這種i == *filenamesSize情況毫無意義。Goroutines 以任意順序執行,所以你不能確定 i == 0 的 goroutine 是最后一個減少 filenamesSize

這可以通過去掉大部分同步原語并在所有 goroutine 完成后簡單地關閉 ch 通道來簡化:

func main() { 

    ch := make(chan map[string][]string)

    var wg WaitGroup


    for _, filename := range getCSVsFromCurrentDir() { 

        filename := filename // capture loop var

        wg.Add(1)

        go func() { 

            checkFile(filename, orders, ch)

            wg.Done()

        }()

    } 


    go func() { 

        wg.Wait() // after all goroutines are done...

        close(ch) // let range loop below exit

    }()


    for str := range ch { 

        // ...

    } 

}


查看完整回答
反對 回復 2022-07-18
?
慕少森

TA貢獻2019條經驗 獲得超9個贊

不是答案,而是一些不適合評論框的評論。


在這部分代碼中


func main() {

    var (

        ordersFile *os.File

        files       Files

        orders     Orders

        err        error

    )


    mu := new(sync.Mutex)

    wg := &sync.WaitGroup{}

    wg.Add(1)

最后一條語句是對 wg.Add 的調用,它看起來是懸空的。我的意思是我們很難理解什么會觸發所需的 wg.Done 對應部分。在沒有 wg.Done 的情況下調用 wg.Add 是一個錯誤,如果不以這樣的方式編寫它們很容易出錯,我們無法立即找到它們。


在代碼的那部分,顯然是錯誤的


    go func(currentFilename string, ch chan<- map[string][]string, i int, orders Orders, wg *sync.WaitGroup, filenamesSize *int, mu *sync.Mutex, done chan<- bool) {

        wg.Add(1)

        defer wg.Done()

考慮到當例程執行時,您將 1 添加到等待組,父例程繼續執行??催@個例子: https: //play.golang.org/p/N9Chaqkv4bd 主程序不等待等待組,因為它沒有時間遞增。


還有更多要說的,但我發現很難理解你的代碼的目的,所以我不確定如何在不重寫它的情況下進一步幫助你。


查看完整回答
反對 回復 2022-07-18
  • 3 回答
  • 0 關注
  • 148 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號