golang中的循环依赖

news2024/11/19 3:31:58

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

导入周期

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

导入循环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不显示导致错误的原因?因为循环中不只有一个罪魁祸首源文件。

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

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

go get github.com/kisielk/godepgraph

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

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

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

导入循环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

底线

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1371433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows10+ubuntu20.04双系统中,ubuntu系统显示home空间不足的扩容方法

实际上网上有两种扩容方法&#xff0c;除了本文的方法外&#xff0c;另一种是在使用启动U盘打开试用ubuntu&#xff0c;应该涉及到nvidia显卡驱动问题故未采用。另一种即本文。 最开始安装双系统时内存分配没有分配好&#xff0c;给ubuntu系统分配的空间较小,导致了后来的的问…

分布式锁3: zk实现分布式锁5 使用中间件curator-interprocessmutex可重入锁

一 curator的说明 1.1 curator的说明 curator是netflix公司开源的一个zk客户端。对Zookeeper提供的原生客户端进行封装&#xff0c;简化了Zookeeper客户端的开发量。Curator解决了很多zookeeper客户端非常底层的细节开发工作&#xff0c;包括连接重连、反复注册wathcer和Node…

玩转Mysql 四(MySQL逻辑架构与数据引擎)

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; 一、MySQL逻辑架构 1、从Oracle收购MySQL后&#xff0c;MySQL逻辑架构受Oracle影响&#xff0c;MyS…

C++与Typescript的区别

目录 一、C类模板和函数模板 1.类模板 2.函数模板 二&#xff0c;Typescript 的泛型声明 1.泛型函数 2.泛型类 为什么C和Typescript语言中主张模板和泛型 一、C类模板和函数模板 在C中&#xff0c;类模板和函数模板允许你为多种数据类型编写通用的代码。这就像每个人都有…

14:00进去,14:05就出来了,面试问的有点变态。。

刚从小厂出来&#xff0c;没想到在另一家公司我又寄了。 在这家公司上班&#xff0c;每天都要加班&#xff0c;但看在钱给的比较多的份上&#xff0c;也就不太计较了。但万万没想到一纸通知&#xff0c;所有人不准加班了&#xff0c;不仅加班费没有了&#xff0c;薪资还要降40…

Jarvis步进法(Jarvis March)凸包算法

Jarvis步进法&#xff08;也称为包裹法&#xff09;&#xff1a; Jarvis步进法是一种逐步选择凸包顶点的算法。从点集中选择一个起始点&#xff0c;然后在每一步中选择下一个顶点&#xff0c;该顶点是当前点集中与当前点形成的线段上&#xff0c;极角最小的点。该算法的时间复杂…

【深度学习:数据增强】计算机视觉中数据增强的完整指南

【深度学习&#xff1a;数据增强】计算机视觉中数据增强的完整指南 为什么要做数据增强&#xff1f;等等&#xff0c;什么是数据增强&#xff1f;数据增强技术数据增强的注意事项和潜在陷阱什么时候应该做数据增强&#xff1f;类不平衡的数据增强那么我应该选择哪些转换呢&…

图像分类任务的可视化脚本,生成类别json字典文件

1. 前言 之前的图像分类任务可视化&#xff0c;都是在train脚本里&#xff0c; 用torch中dataloader将图片和类别加载&#xff0c;然后利用matplotlib库进行可视化。 如这篇文章中&#xff1a;CNN 卷积神经网络对染色血液细胞分类(blood-cells) 在分类任务中&#xff0c;必定…

如何在一张图里同时显示两个三维图

import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3Dplt.rcParams["font.sans-serif"] ["SimHei"]# 正确显示中文和负号 plt.rcParams["axes.unicode_minus"] False# 创建数据 x np.random.rand(50…

就是要你懂网络--一个网络包的旅程

原文地址:https://plantegg.github.io/2019/05/15/%E5%B0%B1%E6%98%AF%E8%A6%81%E4%BD%A0%E6%87%82%E7%BD%91%E7%BB%9C–%E4%B8%80%E4%B8%AA%E7%BD%91%E7%BB%9C%E5%8C%85%E7%9A%84%E6%97%85%E7%A8%8B/ 写在最前面的 我相信你脑子里关于网络的概念都在下面这张图上&#xff0c…

重新认识Elasticsearch-一体化矢量搜索引擎

前言 2023 哪个网络词最热&#xff1f;我投“生成式人工智能”一票。过去一年大家都在拥抱大模型&#xff0c;所有的行业都在做自己的大模型。就像冬日里不来件美拉德色系的服饰就会跟不上时代一样。这不前段时间接入JES&#xff0c;用上好久为碰的RestHighLevelClient包。心血…

深度解析高防产品---高防CDN

高防CDN是一种基于云计算技术的网络安全防御系统&#xff0c;通过在全球范围内部署多个节点&#xff0c;实现对网站内容的加速和保护。其主要作用和功能包括安全防护、加速访问、跨运营商、跨地域的全网覆盖、提高网站的稳定性以及节约成本。高防CDN可以有效地解决不同地区的网…

构建自己的私人GPT-支持中文

上一篇已经讲解了如何构建自己的私人GPT&#xff0c;这一篇主要讲如何让GPT支持中文。 privateGPT 本地部署目前只支持基于llama.cpp 的 gguf格式模型&#xff0c;GGUF 是 llama.cpp 团队于 2023 年 8 月 21 日推出的一种新格式。它是 GGML 的替代品&#xff0c;llama.cpp 不再…

dubbo与seata集成

1.seata是什么? Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 2.seata的注解 GlobalTransactional&#xff1a;全局事务注解&#xff0c;添加了以后可实现分布式事务的回滚和提交&#xff0c;用法与spring…

了解什么是UV纹理?

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 什么是UV&#xff1f; UV 是与几何图形的顶点信息相对应的二维纹理坐…

基于GPT4+Python近红外光谱数据分析及机器学习与深度学习建模

详情点击链接&#xff1a;基于ChatGPT4Python近红外光谱数据分析及机器学习与深度学习建模教程 第一&#xff1a;GPT4基础 1、ChatGPT概述&#xff08;GPT-1、GPT-2、GPT-3、GPT-3.5、GPT-4模型的演变&#xff09; 2、ChatGPT对话初体验&#xff08;注册与充值、购买方法&am…

ESP32_ADC(Arduino)

ADC模数转换 ESP32集成了12位的逐次逼近式ADC&#xff0c;分别为ADC1模块ADC2模块&#xff0c;共支持18个模拟输入通道: ADC1模块&#xff1a;8个通道&#xff0c;32~39ADC2模块&#xff1a;10个通道&#xff0c;0&#xff0c;2&#xff0c;4&#xff0c;12 ~ 15&#xff0c;…

iTOP-3A5000开发板28路PCIE、4路SATA、2路USB2.0、2路USB3.0、LAN、RS232、VGAHDMI等

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA…

创建一个郭德纲相声GPTs

前言 在这篇文章中&#xff0c;我将分享如何利用ChatGPT 4.0辅助论文写作的技巧&#xff0c;并根据网上的资料和最新的研究补充更多好用的咒语技巧。 GPT4的官方售价是每月20美元&#xff0c;很多人并不是天天用GPT&#xff0c;只是偶尔用一下。 如果调用官方的GPT4接口&…