golang中的循环依赖

这篇具有很好参考价值的文章主要介绍了golang中的循环依赖。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作为 Golang 开发人员,您可能遇到过导入周期。Golang 不允许导入循环。如果 Go 检测到代码中的导入循环,则会抛出编译时错误。在这篇文章中,让我们了解导入周期是如何发生的以及如何处理它们。

导入周期

假设我们有两个包,p1并且p2。当 packagep1依赖于 packagep2且 package 又p2依赖于 packagep1时,就会形成依赖循环。或者它可能比这更复杂,例如。packagep2并不直接依赖于 package p1,而是p2依赖于 package ,而 packagep3又依赖于p1,这又是一个循环。

golang中的循环依赖,golang,golang

让我们通过一些示例代码来理解它。

包 p1 :

package p1

import (
	"fmt"
	"import-cycle-example/p2"
)

type PP1 struct{}

func New() *PP1 {
	return &PP1{}
}

func (p *PP1) HelloFromP1() {
	fmt.Println("Hello from package p1")
}

包p2

package p2

import (
	"fmt"
	"import-cycle-example/p1"
)

type PP2 struct{}

func New() *PP2 {
	return &PP2{}
}

func (p *PP2) HelloFromP2() {
	fmt.Println("Hello from package p2")
}

func (p *PP2) HelloFromP1Side() {
	pp1 := p1.New()
	pp1.HelloFromP1()
}

构建时,编译器返回错误:

imports import-cycle-example/p1
imports import-cycle-example/p2
imports import-cycle-example/p1: import cycle not allowed

导入周期是糟糕的设计

Go 高度关注更快的编译时间而不是执行速度(甚至愿意牺牲一些运行时性能来加快构建速度)。Go 编译器不会花费大量时间尝试生成最高效的机器代码,它更关心快速编译大量代码。

允许循环/循环依赖关系将显着增加编译时间,因为每次依赖关系之一发生更改时,整个依赖关系循环都需要重新编译。它还增加了链接时间成本,并使独立测试/重用事物变得困难(单元测试更困难,因为它们不能彼此隔离地进行测试)。循环依赖有时会导致无限递归。

循环依赖还可能导致内存泄漏,因为每个对象都保留另一个对象,它们的引用计数永远不会达到零,因此永远不会成为收集和清理的候选者。

Robe Pike 在回复 Golang 中允许导入周期的提案时表示,这是一个值得预先简单化的领域。进口周期可能很方便,但其成本可能是灾难性的。他们应该继续被禁止。

golang中的循环依赖,golang,golang

调试导入周期

关于导入循环错误最糟糕的是,Golang 不会告诉您导致错误的源文件或部分代码。因此,很难弄清楚代码库何时很大。您可能会想知道不同的文件/包来检查问题到底出在哪里。为什么golang不显示导致错误的原因?因为循环中不只有一个罪魁祸首源文件。

但它确实显示了导致问题的软件包。因此,您可以查看这些软件包并解决问题。

要可视化项目中的依赖关系,您可以使用godepgraph,一个 Go 依赖关系图可视化工具。可以通过运行以下命令来安装:

go get github.com/kisielk/godepgraph

它以Graphviz点格式显示图形。如果您安装了 graphviz 工具(您可以从此处下载),您可以通过将输出管道传输到 dot 来渲染它:

godepgraph -s import-cycle-example | dot -Tpng -o godepgraph.png

您可以在输出 png 文件中看到导入周期:

golang中的循环依赖,golang,golang

除此之外,您还可以使用它go list来获得一些见解(运行go help list以获取更多信息)。

go list -f '{\{join .DepsErrors "\n"\}}' <import-path>

您可以提供导入路径,也可以将当前目录留空。

处理进口周期

当你遇到导入周期错误时,退后一步,思考一下项目组织。处理导入周期的最明显且最常见的方法是通过接口实现。但有时你并不需要它。有时您可能不小心将包裹分成了几个。检查创建导入周期的包是否紧密耦合并且它们需要彼此才能工作,它们可能应该合并到一个包中。在Golang中,包是一个编译单元。如果两个文件必须始终一起编译,则它们必须位于同一个包中。

接口方式:
  • 包通过导入 packagep1来使用包中的函数/变量。p2p2
  • p2可以从包中调用函数/变量,p1而无需导入包p1。所有需要传递的包p1实例都实现了 中定义的接口p2,这些实例将被视为包p2对象。

这就是 packagep2忽略 package 的存在p1并且导入周期被破坏的方式。

应用上述步骤后,打包p2代码:

package p2

import (
	"fmt"
)

type pp1 interface {
	HelloFromP1()
}

type PP2 struct {
	PP1 pp1
}

func New(pp1 pp1) *PP2 {
	return &PP2{
		PP1: pp1,
	}
}

func (p *PP2) HelloFromP2() {
	fmt.Println("Hello from package p2")
}

func (p *PP2) HelloFromP1Side() {
	p.PP1.HelloFromP1()
}

p1代码如下所示:

package p1

import (
	"fmt"
	"import-cycle-example/p2"
)

type PP1 struct{}

func New() *PP1 {
	return &PP1{}
}

func (p *PP1) HelloFromP1() {
	fmt.Println("Hello from package p1")
}

func (p *PP1) HelloFromP2Side() {
	pp2 := p2.New(p)
	pp2.HelloFromP2()
}

您可以使用main包中的此代码进行测试。

package main

import (
	"import-cycle-example/p1"
)

func main() {
	pp1 := p1.PP1{}
	pp1.HelloFromP2Side() // Prints: "Hello from package p2"
}

您可以在 GitHub 上的jogendra/import-cycle-example-go上找到完整的源代码

使用接口打破循环的其他方法可以是将代码提取到单独的第三个包中,该包充当两个包之间的桥梁。但很多时候它会增加代码重复。您可以采用这种方法,同时牢记您的代码结构。

“三通”进口链:包 p1 -> 包 m1 & 包 p2 -> 包 m1

丑陋的方式:

有趣的是,您可以通过使用go:linknamego:linkname是编译器指令(用作//go:linkname localname [importpath.name])。此特殊指令不适用于其后面的 Go 代码。相反,//go:linkname指令指示编译器使用“importpath.name”作为源代码中声明为“localname”的变量或函数的目标文件符号名称。(定义来自golang.org,乍一看很难理解,看下面的源代码链接,我尝试用它解决导入循环。)

有许多 Go 标准包依赖于使用go:linkname. 有时您还可以使用它解决代码中的导入周期问题,但您应该避免使用它,因为它仍然是一种 hack,并且 Golang 团队不推荐。

这里需要注意的是,Golang 标准包不是用来go:linkname避免导入周期的,而是用它来避免导出不应该公开的 API。

这是我使用以下方法实现的解决方案的源代码go:linkname

-> jogendra/import-cycle-example-go -> golinkname

底线

当代码库很大时,导入周期绝对是一件痛苦的事情。尝试分层构建应用程序。较高层应该导入较低层,但较低层不应该导入较高层(它会产生循环)。记住这一点,有时将紧密耦合的包合并到一个包中是比通过接口解决问题更好的解决方案。但对于更一般的情况,接口实现是打破导入周期的好方法。文章来源地址https://www.toymoban.com/news/detail-819233.html

到了这里,关于golang中的循环依赖的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • spring 的循环依赖以及spring为什么要用三级缓存解决循环依赖

    spring 的循环依赖以及spring为什么要用三级缓存解决循环依赖

            bean的生命周期         这里简单过一下 class -无参构造 -普通对象 -依赖注入(对加了autowire等的属性赋值) -初始化前-初始化 -初始化后(aop) -放入单例池的map(一级缓存) -bean对象 这里提一点单例bean单例bean 其实就是用mapbeanName,Bean对象创建的,多例bean就不

    2024年02月15日
    浏览(14)
  • golang依赖注入工具digo

    digo工具地址:https://github.com/werbenhu/digo 使用注释中的注解 自动代码生成 自动检测循环依赖 编译时期依赖注入 自动初始化 支持实例组的管理 更多示例请参考:examples 打开命令行执行下面命令, digogen 将会根据注解自动生成 digo.generated.go 源码文件. go run .digo.generated.go .main.

    2024年02月07日
    浏览(10)
  • Spring解决循环依赖

    Spring解决循环依赖

    目录 什么是spring循环依赖 什么情况下循环依赖可以被处理? spring 如何解决循环依赖 创建A这个Bean的流程 答疑 疑问:在给B注入的时候为什么要注入一个代理对象? 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗? 三级

    2024年02月22日
    浏览(11)
  • 【Spring】循环依赖

    【Spring】循环依赖

    循环依赖是什么? 循环依赖的场景有哪一些? 三级缓存分别是什么?三个Map有什么异同? 三级缓存是如何解决循环依赖的? 为什么要使用三级缓存?为什么不可以用二级缓存? 为什么构造器循环依赖、原型Bean循环依赖无法用三级缓存解决? 看过 Spring源码吗?一般我们说

    2024年02月21日
    浏览(11)
  • springboot循环依赖

    springboot循环依赖

    2024年02月10日
    浏览(12)
  • Spring循环依赖处理

    Spring循环依赖处理

    循环依赖是指两个或多个组件之间相互依赖,形成一个闭环,从而导致这些组件无法正确地被初始化或加载。这种情况可能会在软件开发中引起问题,因为循环依赖会导致初始化顺序混乱,组件之间的关系变得复杂,甚至可能引发死锁或其他不稳定行为。 在编程中,循环依赖

    2024年02月07日
    浏览(10)
  • SpringBoot 循环依赖,如何解决?

    循环依赖是指在Spring Boot 应用程序中,两个或多个类之间存在彼此依赖的情况,形成一个循环依赖链。 在这种情况下,当一个类在初始化时需要另一个类的实例,而另一个类又需要第一个类的实例时,就会出现循环依赖问题。这会导致应用程序无法正确地初始化和运行,因为

    2024年02月01日
    浏览(11)
  • Spring解决循环依赖问题

    Spring解决循环依赖问题

    例如,就是A对象依赖了B对象,B对象依赖了A对象。(下面的代码属于 属性的循环依赖 ,也就是初始化阶段的循环依赖,区别与底下 构造器的循环依赖 ) 问题来了: A Bean创建 —— 依赖了 B 属性 ——  触发 B Bean创建 ——  B 依赖了 A 属性 ——  需要 A Bean(但A Bean还在创建

    2024年02月12日
    浏览(13)
  • 拓扑排序实现循环依赖判断

    本文记录如何通过拓扑排序,实现循环依赖判断 一般提到循环依赖,首先想到的就是Spring框架提供的Bean的循环依赖检测,相关文档可参考: https://blog.csdn.net/cristianoxm/article/details/113246104 本文方案脱离Spring Bean的管理,通过算法实现的方式,完成对象循环依赖的判断,涉及的

    2024年02月05日
    浏览(10)
  • Golang for 循环

    Golang for 循环

    Go(Golang)编程语言中的“for”循环是一个基本而多功能的结构,用于迭代集合、重复执行代码块以及管理循环控制流。Golang的“for”循环语法简洁却强大,为处理多样的循环场景提供了一系列能力。无论是遍历数组和切片,还是利用条件语句,Golang中“for”循环的简单性和

    2024年02月21日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包