当前位置:首页 > 文章列表 > Golang > Go问答 > 使用 waitgroup 程序陷入死锁

使用 waitgroup 程序陷入死锁

来源:stackoverflow 2024-04-21 23:18:36 0浏览 收藏

学习知识要善于思考,思考,再思考!今天golang学习网小编就给大家带来《使用 waitgroup 程序陷入死锁》,以下内容主要包含等知识点,如果你正在学习或准备学习Golang,就都不要错过本文啦~让我们一起来看看吧,能帮助到你就更好了!

问题内容

我正在编写一个程序,该程序读取名为orders.csv 的文件中的订单号列表,并将其与文件夹中存在的其他 csv 文件进行比较。

问题是,即使使用 waitgroup,它也会陷入死锁,我不知道为什么。

由于某种原因,stackoverflow 说我的帖子主要是代码,所以我必须添加这一行,因为如果有人想帮助我调试我遇到的这个问题,整个代码是必要的。

package main

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

// getCSVsFromCurrentDir returns a string slice
// with the filenames of csv files inside the
// current directory that are not "orders.csv"
func getCSVsFromCurrentDir() []string {
    var filenames []string

    err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
        if path != "." && strings.HasSuffix(path, ".csv") && path != ordersFilename {
            filenames = append(filenames, path)
        }

        return nil
    })

    if err != nil {
        log.Fatalln("Could not read file names in current dir")
    }

    return filenames
}

// getOrderIDs returns an Orders struct filled
// with order IDs retrieved from the file
func getOrderIDs(file *os.File) Orders {
    var (
        orders      Orders
        err         error
        fileContent string
    )

    reader := bufio.NewReader(file)

    if fileContent, err = readLine(reader); err != nil {
        log.Fatalln("Could not read file: " + ordersFilename)
    }

    for err == nil {
        orders.ID = append(orders.ID, fileContent)
        fileContent, err = readLine(reader)
    }

    return orders
}

func checkFile(filename string, orders Orders, ch chan<- map[string][]string) {
    var (
        err           error
        file          *os.File
        fileContent   string
        orderFilesMap map[string][]string
        counter       int
    )

    orderFilesMap = make(map[string][]string)

    if file, err = os.Open(filename); err != nil {
        log.Fatalln("Could not read file: " + filename)
    }

    reader := bufio.NewReader(file)

    if fileContent, err = readLine(reader); err != nil {
        log.Fatalln("Could not read file: " + filename)
    }

    for err == nil {
        if containedInSlice(fileContent, orders.ID) && !containedInSlice(fileContent, orderFilesMap[filename]) {
            orderFilesMap[filename] = append(orderFilesMap[filename], fileContent)
            // fmt.Println("Found: ", fileContent, " in ", filename)
        } else {
            // fmt.Printf("Could not find: '%s' in '%s'\n", fileContent, filename)
        }
        counter++
        fileContent, err = readLine(reader)
    }

    ch <- orderFilesMap
}

// containedInSlice returns true or false
// based on whether the string is contained
// in the slice
func containedInSlice(str string, slice []string) bool {
    for _, ID := range slice {
        if ID == str {
            return true
        }
    }

    return false
}

// readLine returns a line from the passed reader
func readLine(r *bufio.Reader) (string, error) {
    var (
        isPrefix bool  = true
        err      error = nil
        line, ln []byte
    )
    for isPrefix && err == nil {
        line, isPrefix, err = r.ReadLine()
        ln = append(ln, line...)
    }
    return string(ln), err
}

解决方案


  1. 第一个问题是 wg.add 始终必须位于它代表的 goroutine 之外。如果不是,则 wg.wait 调用可能会在 goutine 实际开始运行之前调用(并称为 wg.add),因此会“思考” 没有什么可以等待的。

  2. 代码的第二个问题是它有多种方式等待例程完成。有 waitgroup 并且有 done 通道。仅使用其中之一。哪一个还取决于结果如何 使用 goroutine。我们来讨论下一个问题。

  3. 第三个问题是收集结果。目前,代码仅打印/使用 goroutine 的单个结果。 在 select 周围放置一个 for { ... } 循环,并在 done 通道关闭时使用 return 跳出循环。 (请注意,您不需要在 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

  1. 对于您的情况,我们有一些优势。我们确切地知道我们启动了多少个 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)
    }
}

简单多了,不是吗?希望有帮助!

这段代码有很多错误。

  1. 您使用的 waitgroup 是错误的。 add 必须在主 goroutine 中调用,否则有可能在所有 add 调用完成之前调用 wait。
  2. 初始化 waitgroup 后立即进行了一个无关的 add(1) 调用,该调用与 done() 调用不匹配,因此 wait 永远不会返回(假设上面的点已修复)。
  3. 您正在使用 waitgroup 和 done 通道来表示完成。这充其量是多余的。
  4. 您在未持有锁的情况下读取 filenamessize(在 if i == *filenamessize 语句中)。这是一个竞争条件。
  5. i == *filenamessize 条件首先没有任何意义。 goroutine 以任意顺序执行,因此你无法确定 i == 0 的 goroutine 是最后一个减少 filenamessize 的 goroutine

这一切都可以通过摆脱大部分 if 同步原语并在所有 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 { 
        // ...
    } 
}

到这里,我们也就讲完了《使用 waitgroup 程序陷入死锁》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
小米SU7上市受热捧,交付时间最长或达6个月小米SU7上市受热捧,交付时间最长或达6个月
上一篇
小米SU7上市受热捧,交付时间最长或达6个月
springboot项目数据库密码怎么实现加密
下一篇
springboot项目数据库密码怎么实现加密
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • SEO标题魔匠AI:高质量学术写作平台,毕业论文生成与优化专家
    魔匠AI
    SEO摘要魔匠AI专注于高质量AI学术写作,已稳定运行6年。提供无限改稿、选题优化、大纲生成、多语言支持、真实参考文献、数据图表生成、查重降重等全流程服务,确保论文质量与隐私安全。适用于专科、本科、硕士学生及研究者,满足多语言学术需求。
    10次使用
  • PPTFake答辩PPT生成器:一键生成高效专业的答辩PPT
    PPTFake答辩PPT生成器
    PPTFake答辩PPT生成器,专为答辩准备设计,极致高效生成PPT与自述稿。智能解析内容,提供多样模板,数据可视化,贴心配套服务,灵活自主编辑,降低制作门槛,适用于各类答辩场景。
    26次使用
  • SEO标题Lovart AI:全球首个设计领域AI智能体,实现全链路设计自动化
    Lovart
    SEO摘要探索Lovart AI,这款专注于设计领域的AI智能体,通过多模态模型集成和智能任务拆解,实现全链路设计自动化。无论是品牌全案设计、广告与视频制作,还是文创内容创作,Lovart AI都能满足您的需求,提升设计效率,降低成本。
    25次使用
  • 美图AI抠图:行业领先的智能图像处理技术,3秒出图,精准无误
    美图AI抠图
    美图AI抠图,依托CVPR 2024竞赛亚军技术,提供顶尖的图像处理解决方案。适用于证件照、商品、毛发等多场景,支持批量处理,3秒出图,零PS基础也能轻松操作,满足个人与商业需求。
    35次使用
  • SEO标题PetGPT:智能桌面宠物程序,结合AI对话的个性化陪伴工具
    PetGPT
    SEO摘要PetGPT 是一款基于 Python 和 PyQt 开发的智能桌面宠物程序,集成了 OpenAI 的 GPT 模型,提供上下文感知对话和主动聊天功能。用户可高度自定义宠物的外观和行为,支持插件热更新和二次开发。适用于需要陪伴和效率辅助的办公族、学生及 AI 技术爱好者。
    36次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码