当前位置:首页 > 文章列表 > Golang > Go问答 > Codewalk之Golang并发代码回顾

Codewalk之Golang并发代码回顾

来源:stackoverflow 2024-03-16 20:03:33 0浏览 收藏

**摘要** 本文审阅了 Go 语言中并发代码的最佳实践,以了解 Golang codewalks 中的示例代码。该代码展示了无 goroutine 清理逻辑、作者没有创建写入通道以及异步写入通道等问题。作者认为,通道的清理和写入必须同步,并建议作者负责关闭通道。尽管示例代码旨在用于教学目的,但它违背了一些最佳实践,例如:没有清理 goroutine、通道创建和写入逻辑未由单个实体控制,以及异步写入通道。

问题内容

我正在尝试了解 golang 并发的最佳实践。我读了 o'reilly 的关于 go 并发的书,然后又回到了 golang codewalks,特别是这个例子:

https://golang.org/doc/codewalk/sharemem/

这是我希望与您一起回顾的代码,以便更多地了解 go。我的第一印象是这段代码破坏了一些最佳实践。这当然是我(非常)没有经验的观点,我想讨论并获得对这个过程的一些见解。这不是谁对谁错的问题,请保持友善,我只是想分享我的观点并获得一些反馈。也许这次讨论会帮助其他人了解我为什么错了并教给他们一些东西。

我完全意识到这段代码的目的是为了教导初学者,而不是成为完美的代码。

问题 1 - 无 goroutine 清理逻辑

func main() {
    // create our input and output channels.
    pending, complete := make(chan *resource), make(chan *resource)

    // launch the statemonitor.
    status := statemonitor(statusinterval)

    // launch some poller goroutines.
    for i := 0; i < numpollers; i++ {
        go poller(pending, complete, status)
    }

    // send some resources to the pending queue.
    go func() {
        for _, url := range urls {
            pending <- &resource{url: url}
        }
    }()

    for r := range complete {
        go r.sleep(pending)
    }
}

main 方法无法清理 goroutines,这意味着如果这是库的一部分,它们就会被泄漏。

问题 2 - 作家没有催生频道

我读到,作为最佳实践,创建写入清理通道的逻辑应该由单个实体控制(或实体组)。这背后的原因是写入者在写入封闭通道时会感到恐慌。因此,编写者最好创建通道、写入通道并控制何时关闭通道。如果有多个writer,可以使用waitgroup进行同步。

func statemonitor(updateinterval time.duration) chan<- state {
    updates := make(chan state)
    urlstatus := make(map[string]string)
    ticker := time.newticker(updateinterval)
    go func() {
        for {
            select {
            case <-ticker.c:
                logstate(urlstatus)
            case s := <-updates:
                urlstatus[s.url] = s.status
            }
        }
    }()
    return updates
}

此函数不应该负责创建更新通道,因为它是通道的读取器,而不是写入器。该通道的编写者应该创建它并将其传递给该函数。基本上是对函数说“我将通过此通道向您传递更新”。但相反,这个函数正在创建一个通道,并且不清楚谁负责清理它。

问题 3 - 异步写入通道

这个函数:

func (r *resource) sleep(done chan<- *resource) {
    time.sleep(pollinterval + errtimeout*time.duration(r.errcount))
    done <- r
}

此处引用:

for r := range complete {
    go r.Sleep(pending)
}

这似乎是一个糟糕的主意。当这个通道关闭时,我们将有一个 goroutine 休眠在我们无法到达的地方,等待写入该通道。假设这个 goroutine 休眠了 1 小时,当它醒来时,它将尝试写入在清理过程中关闭的通道。这是为什么频道的作者应该负责清理过程的另一个例子。在这里,我们有一位完全自由的作家,他不知道频道何时关闭。

如果我错过了该代码中的任何问题(与并发相关),请列出它们。这不一定是一个客观问题,如果您出于任何原因以不同的方式设计代码,我也有兴趣了解它。

这段代码的最大教训

对我来说,从审查这段代码中得到的最大教训是通道的清理和对通道的写入必须同步。它们必须处于相同的 for{} 状态,或者至少以某种方式进行通信(可能通过其他通道或原语)以避免写入关闭的通道。


正确答案


  1. 它是 main 方法,因此不需要清理。当main返回时,程序退出。如果这不是 main,那么你就是对的。

  2. 不存在适合所有用例的最佳实践。您在此处显示的代码是一种非常常见的模式。该函数创建一个 goroutine,并返回一个通道,以便其他人可以与该 goroutine 进行通信。没有规则规定如何创建渠道。但没有办法终止该 goroutine。这种模式非常适合的一个用例是从 数据库。该通道允许从读取的数据流式传输 数据库。在这种情况下,通常有其他方法可以终止 goroutine 不过,就像传递上下文一样。

  3. 同样,对于如何创建/关闭通道没有硬性规则。通道可以保持打开状态,当不再使用时它将被垃圾收集。如果用例需要的话,通道可以无限期地保持开放,并且您担心的场景永远不会发生。

  1. 当您询问此代码是否是库的一部分时,是的,在库函数内部不进行清理的情况下生成 goroutine 是很糟糕的做法。如果这些 goroutine 执行库中记录的行为,那么调用者不知道该行为何时会发生就会出现问题。如果您有任何典型的“即发即忘”行为,则应该由呼叫者选择何时忘记它。例如:
func doafter5minutes(f func()) {
   go func() {
       time.sleep(5 * time.minute)
       f()
       log.println("done!")
   }()
}

有道理吧?当您调用该函数时,它会在 5 分钟后执行某些操作。问题是这个函数很容易被误用,如下所示:

// do the important task every 5 minutes
for {
    doafter5minutes(importanttaskfunction)
}

乍一看,这似乎没问题。我们每 5 分钟就会执行一次重要任务,对吧?事实上,我们很快就会生成许多 goroutine,可能会在它们开始消失之前耗尽所有可用内存。

我们可以实现某种回调或通道来在任务完成时发出信号,但实际上,该函数应该像这样简化:

func doafter5minutes(f func()) {
   time.sleep(5 * time.minute)
   f()
   log.println("done!")
}

现在调用者可以选择如何使用它:

// call synchronously
doafter5minutes(importanttaskfunction)
// fire and forget
go doafter5minutes(importanttaskfunction)
  1. 这个功能可以说也应该改变。正如您所说,作者应该有效地拥有该频道,因为他们应该是关闭该频道的人。事实上,这个通道读取函数坚持创建它读取的通道,实际上迫使自己陷入上面提到的这种糟糕的“即发即忘”模式。请注意该函数如何需要从通道读取,但它还需要在读取之前返回通道。因此,它必须将读取行为放入一个新的、非托管的 goroutine 中,以允许自己立即返回通道。
95578​​4645965

请注意,该功能现在更简单、更灵活且同步。先前版本真正完成的唯一一件事是,它(大部分)保证 statemonitor 的每个实例都将拥有一个属于自己的通道,并且不会出现多个监视器在同一通道上竞争读取的情况。虽然这可能可以帮助您避免某些类型的错误,但它也使该功能的灵活性大大降低,并且更有可能出现资源泄漏。

  1. 我不确定我是否真的理解这个例子,但是关闭通道的黄金法则是作者应该始终负责关闭通道。请记住这条规则,并注意有关此代码的几点:
  • sleep 方法写入 r
  • sleep 方法是并发执行的,没有方法跟踪正在运行的实例数量、它们所处的状态等。

仅基于这些点,我们可以说程序中可能没有任何地方可以安全地关闭 r,因为似乎没有办法知道它是否会被使用再次。

理论要掌握,实操不能落!以上关于《Codewalk之Golang并发代码回顾》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
win7玩游戏时CPU自动降频怎么解决?win7玩游戏时CPU自动降频怎么解决?
上一篇
win7玩游戏时CPU自动降频怎么解决?
将框架 Gin 切换为 Echo 后服务器不再响应
下一篇
将框架 Gin 切换为 Echo 后服务器不再响应
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • 笔灵AI生成答辩PPT:高效制作学术与职场PPT的利器
    笔灵AI生成答辩PPT
    探索笔灵AI生成答辩PPT的强大功能,快速制作高质量答辩PPT。精准内容提取、多样模板匹配、数据可视化、配套自述稿生成,让您的学术和职场展示更加专业与高效。
    24次使用
  • 知网AIGC检测服务系统:精准识别学术文本中的AI生成内容
    知网AIGC检测服务系统
    知网AIGC检测服务系统,专注于检测学术文本中的疑似AI生成内容。依托知网海量高质量文献资源,结合先进的“知识增强AIGC检测技术”,系统能够从语言模式和语义逻辑两方面精准识别AI生成内容,适用于学术研究、教育和企业领域,确保文本的真实性和原创性。
    41次使用
  • AIGC检测服务:AIbiye助力确保论文原创性
    AIGC检测-Aibiye
    AIbiye官网推出的AIGC检测服务,专注于检测ChatGPT、Gemini、Claude等AIGC工具生成的文本,帮助用户确保论文的原创性和学术规范。支持txt和doc(x)格式,检测范围为论文正文,提供高准确性和便捷的用户体验。
    38次使用
  • 易笔AI论文平台:快速生成高质量学术论文的利器
    易笔AI论文
    易笔AI论文平台提供自动写作、格式校对、查重检测等功能,支持多种学术领域的论文生成。价格优惠,界面友好,操作简便,适用于学术研究者、学生及论文辅导机构。
    50次使用
  • 笔启AI论文写作平台:多类型论文生成与多语言支持
    笔启AI论文写作平台
    笔启AI论文写作平台提供多类型论文生成服务,支持多语言写作,满足学术研究者、学生和职场人士的需求。平台采用AI 4.0版本,确保论文质量和原创性,并提供查重保障和隐私保护。
    41次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码