golang中的循环依赖

作为 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

底线

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

phpcms v9后台添加草稿箱功能

一、后台添加文章模板phpcms/modules/content/templates/content_add.tpl.php中94行增加”保存草稿“按钮&#xff1a; <div class"button"><input value"<?php echo L(save_draft);?>" type"submit" name"dosubmit_draf…

Qt5插件开发入门+示例

目的 1、为什么用插件 现在大家最讲模块化开发了,怎么算模块化,分成不同的类,分成不同的文件夹,高内聚,低耦合,这个当然算是。 从高层次讲,它们是在一起的,只是逻辑上的模块化,不是物理上的模块化,或者说不是彻底的模块化,彻底的模块化应该像一个辆自行车一样,车…

深度数据恢复,3个有效方法要掌握!

“我在电脑里保存了部分很重要的数据&#xff0c;但是不知道怎么就误删了它们&#xff0c;大家有什么比较简单的操作可以恢复这些被深度删除的数据吗&#xff1f;” 在数字化时代&#xff0c;我们的生活与工作已与数据紧密相连&#xff0c;这给我们带来了很多的便利。但不可否认…

Queue接口分析

一、Queue是什么 该接口是Java集合框架成员 Queue&#xff1a; 通常&#xff08;但不一定&#xff09;队列就是一个先入先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;和堆一样&#xff08;但可以进行转换&#xff0c;比如优先级列队排序&#xff0c;又或者改为栈形…

功能分享【电商API接口】:商品采集正确使用方法!

相信很多做过电商的人&#xff0c;曾经有在淘宝、京东、天猫、拼多多、1688等平台上卖过自己的产品&#xff0c;但是每换一个平台&#xff0c;商品要重新上传&#xff0c;这浪费了很多没有必要的时间。 为此我们开发了商品采集API功能&#xff0c;一键完成上架商品&#xff0c…

代码随想录第五十天——买卖股票的最佳时机|||,买卖股票的最佳时机IV

leetcode 123. 买卖股票的最佳时机||| 题目链接&#xff1a;买卖股票的最佳时机||| 本题关键在于至多买两次&#xff0c;代表可以买卖0&#xff0c;1&#xff0c;2次。 确定dp数组以及下标的含义 一天一共可以有五个状态&#xff1a; 0.没有操作 &#xff08;可以不设置这个状…

云计算任务调度仿真02

前面已经分享过一个仿真项目&#xff0c;但是基于policy gradient方法实现的&#xff0c;考虑到许多人从零到一实现DQN方法有点难度&#xff0c;所以这次分享一个基于DQN实现的仿真项目&#xff0c;非常简单。 这里之所以简单主要得益于它是用pytorch实现的&#xff0c;而pyto…

Prometheus监控遇上报错invalid is not a valid start token

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 问题描述&#xff1a; 使用prometheus采集java应用的metric指标数据&#xff0c;在prometheus界面pod状态为down&#xff0c;报…

clickhouse常规的优化方法

一、建表优化 1.1日期字段避免使用String存储 建表时能用数值型或日期时间型表示的字段就不要用字符串&#xff0c;全String 类型在以Hive 为中心的数仓建设中常见&#xff0c;但ClickHouse 环境不应受此影响。 虽然ClickHouse 底层将DateTime 存储为时间戳Long 类型&#xf…

063:vue中一维数组与三维数组联动,类似购物车增减

第063个 查看专栏目录: VUE ------ element UI javascript 一维数组与三维数组联动,一维数组转换为三为数组,源文件下载 .zip 专栏目标 在vue和element UI联合技术栈的操控下,本专栏提供行之有效的源代码示例和信息点介绍,做到灵活运用。 (1)提供vue2的一些基本操作:安…

熟悉HDFS常用操作

1. 利用Hadoop提供的Shell命令完成下列任务 (1)向HDFS中上传任意文本文件,如果指定的文件在HDFS中已经存在,由用户指定是追加到原有文件末尾还是覆盖原有的文件。 #检查文件是否存在./bin/hdfs dfs -test -e text.txt echo $? #结果是1 代表已存在 #根据结果判断出文件已存…

[ 机器学习 ] 关于Jupyter Notebook中pytorch模块import失败的问题

0x01、问题描述 在使用WSL搭建Jupyter进行代码测试的时候 发现Miniconda&#xff08;虚拟环境均适用&#xff09;中安装的pytorch在Jupyter里面import失败 但在python解释器的命令模式里可以测试import成功 并且torch.cuda_available()打印True 以前用的是IDEA没怎么用Jup…

Python学习笔记-使用Anaconda+VSCode配置开发环境

文章目录 概述一、安装Anaconda1.1 下载软件1.2 安装anaconda1.3 配置环境 二、配置虚拟环境2.1 使用conda创建一个新的虚拟环境2.1.1 使用search指令查看支持的python的版本&#xff1a;2.1.2 使用create创建指定版本的虚拟环境&#xff1a;2.1.3 使用env list查看虚拟环境列表…

文件夹重命名技巧:如何通过重命名解决文件夹名混乱不规律的问题

在日常生活和工作中&#xff0c;我们经常需要管理大量的文件夹&#xff0c;整理文档、图片等其他类型的文件。随着时间的推移&#xff0c;文件夹名可能会变得混乱和不规律&#xff0c;导致查找和管理变得困难。现在一起来看云炫文件管理器如何让文件名变简洁的操作方法吧。 下…

【2024最新-python3小白零基础入门】No1.python简介以及环境搭建

文章目录 一 python3 简介二 python语言的特点三 python安装四 安装开发工具-pycharm五 新建一个python项目1.新建项目2 配置虚拟环境3 运行项目 一 python3 简介 Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&a…

Js-基础语法(二)

运算符 赋值运算符 赋值运算符&#xff1a;对变量进行赋值的运算符 已经学过的赋值运算符&#xff1a; 将等号右边的值赋予给左边, 要求左边必须是一个容器 其他赋值运算符&#xff1a; - */% 使用这些运算符可以在对变量赋值时进行快速操作 一元运算符 众多的 JavaScrip…

使用Linux防火墙管理HTTP流量

在Linux系统中&#xff0c;防火墙是用于控制网络流量的重要工具。通过防火墙&#xff0c;你可以根据需要限制、过滤或允许特定的网络流量&#xff0c;从而提高系统的安全性。在处理HTTP流量时&#xff0c;防火墙可以帮助你实施访问控制、流量监控和其他安全策略。 iptables i…

CSS3新增边框样式

边框样式 概念:在CSS3中&#xff0c;针对元素边框增加了丰富的修饰属性。 常见的边框样式属性有以下 属性说明border-radius圆角效果box-shadow边框阴影border-image边框背景 border-radius属性 概念&#xff1a;border-radius属性可以为元素添加圆角效果 语法&#xff1…

新书速览|循序渐进Vue.js 3.x前端开发实战

Vue.js初学者和前端开发人员使用&#xff0c;网课、培训机构与大中专院校的教学用书 作者简介 张益珲 美国亚利桑那州立大学计算机工程技术硕士&#xff0c;架构师&#xff0c;从业近10年&#xff0c;多年大前端开发经验&#xff0c;曾就职于知名上市公司&#xff0c;主导开发…

Java项目:115SSM宿舍管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 宿舍管理系统基于SpringSpringMVCMybatis开发&#xff0c;系统主要功能如下&#xff1a; 学生管理班级管理宿舍管理卫生管理维修登记访客管理 二、技术框…