从 Context 看 Go 设计模式:接口、封装和并发控制

在这里插入图片描述

文章目录

    • Context 的基本结构
    • Context 的实现和传递机制
    • 为什么 Context 不直接传递指针
    • 案例:DataStore
    • 结论

在 Go 语言中, context 包是并发编程的核心,用于传递取消信号和请求范围的值。但其传值机制,特别是为什么不通过指针传递,而是通过接口,引发了我的一些思考。

考虑以下典型的代码片段:

package mainimport "context"func main() {ctx, cancel := context.WithCancel(context.Background())// ... call cancel() when specified signals are triggeredhandle(ctx)
}func handle(ctx context.Context) error {return nil
}

这段代码展示了在 Go 中创建和传递 context 的简单用法。但背后的设计理念和实现细节却值得研究。

为什么 context 是以接口的形式传递,而非指针?这不仅涉及 Go 的并发哲学,还关系到封装性、并发安全性和接口的灵活性。

本文将简要探讨 context 包的设计和实现,着重解析其非指针传值的原因,从而揭示 Go 并发模型背后的设计智慧。

Context 的基本结构

首先,如上的代码中,通过 context.WithCancel(context.Background()) 返回的是一个 context.Context 类型,而需要明确的是,context.Context 是一个接口,而不是一个具体的数据结构。

在这里插入图片描述

这个接口定义了四个方法:Deadline、Done、Err 和 Value。这些方法提供了控制和访问 context 的手段。

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}

Context 的实现和传递机制

在 Go 中,context 的实现是通过结构体和指针的组合完成。例如,WithCancel 函数返回的 context.Context 类型实际上是一个指向 cancelCtx 结构体的指针。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }
}

这里的关键在于理解 Go 语言的传值机制。

在 Go 中,所有的函数参数都是通过值传递的。这意味着传递给函数的总是数据的副本,而不是数据本身。

然而,当你传递一个指针时,你传递的是指针的副本,这个副本指向原始数据。因此,即使 Go 语言只有值传递,你仍然可以通过传递指针的副本来修改原始数据。

在调用方,我们拿到 WithCancel 返回的指针,因为它的内部实现,满足 context.Context 的接口约束,能成功转为 Context 接口类型。

为什么 Context 不直接传递指针

虽然 context 的某些实现(如 cancelCtx)在内部使用指针,但 context.Context 接口本身并不暴露任何指针。

为什么要这么做呢?

在这里插入图片描述

封装性

通过将具体结构隐藏在接口后面,context 包确保了用户不能直接访问或修改内部状态,这是良好封装的标志。如其中的 timerCtx 保存了时间信息,而 valueCtx 保存了请求范围了的上下文信息,这些数据保证一致性和并发安全。

这种设计防止了不恰当的使用,保持了 context 的行为一致性和预测性。

并发安全

context 被设计为并发安全的。如果 context 通过指针传递,暴露内部实现,那么在并发访问时,可能就有方式修改实际数据的内部状态。

通过接口隐藏实现细节,context 的设计者可以确保内部状态的同步和一致性,而不需要用户介入。

灵活性

context.Context 是一个接口,这意味着你可以有多种实现。

如果 context 通过指针传递,那么所有的实现都必须是具体的结构体,如 handle 函数指定传递 cancelCtx 的话,那就不能传递 timerCtxvalueCtx 等其他类型 Context 实现类。而通过使用接口,Go 语言允许更多的灵活性和实现多样性。

我们已经完成了 context 包设计理念的探讨,尤其是它如何通过接口和封装来保证并发安全性,同时提供清晰的抽象。

最后,让我们通过一个具体的例子来展示 Go 语言的这种设计模型。

案例:DataStore

我们要实现一个 datastore 实现存储数据,要求同时提供两种版本的实现:并发安全和无并发安全版本。

代码片段如下所示,它展示了 DataStore 接口的两种不同实现:safeDataStoreinMemoryDataStore

DataStore 是一个接口,定义了对数据的操作。

在这里插入图片描述

DataStore 是一个接口的具体代码,如下所示:

type DataStore interface {ReadData() stringWriteData(data string)
}

safeDataStore 是一个实现了 DataStore 接口的结构体,它使用 sync.Mutex 来保证并发安全.

type safeDataStore struct {mu   sync.Mutexdata string
}func (ds *safeDataStore) ReadData() string {ds.mu.Lock()defer ds.mu.Unlock()return ds.data
}func (ds *safeDataStore) WriteData(data string) {ds.mu.Lock()defer ds.mu.Unlock()ds.data = data
}

inMemoryDataStore 是另一个实现了 DataStore 接口的结构体,它假设只在单个 goroutine 中使用,因此不需要同步机制


type inMemoryDataStore struct {data string
}func (ds *inMemoryDataStore) ReadData() string {return ds.data
}func (ds *inMemoryDataStore) WriteData(data string) {ds.data = data
}

如上的代码所示,DataStore 接口定义了数据存储的基本操作。同时,我们提供了两种实现:

  • safeDataStore 使用互斥锁来保证并发安全,适用于并发场景;
  • inMemoryDataStore 只在单 goroutine 中使用,不涉及任何同步机制,适用于简单场景。

使用这两个实现的代码如下:

func main() {var store DataStore// 使用 safeDataStorestore = &safeDataStore{}store.WriteData("safe data")fmt.Println(store.ReadData())// 使用 inMemoryDataStorestore = &inMemoryDataStore{}store.WriteData("in-memory data")fmt.Println(store.ReadData())
}

通过这个例子,可以看到 Go 语言是如何通过这种模式来支持多样性和灵活性的。不同的 DataStore 实现可以有不同的内部行为和性能特性,但它们对外提供了统一接口。这种设计不仅使代码模块化和易于维护,而且更加易于扩展性。

结论

总结来说,无论是在 context 包的设计中,还是在我们的 DataStore 示例中,Go 语言的接口和封装都展现了其在构建并发安全且易于维护的系统方面的强大能力。通过这些机制,Go 语言为开发者提供了一种清晰、一致且灵活的方式来管理和传递程序的状态和行为。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

防伪技术行业研究:年复合增长率约为10%

近年来&#xff0c;我国各种新的防伪技术不断涌现&#xff0c;部分防伪技术已经达到国际先进水平&#xff0c;并广泛应用于产品防伪、票证防伪等领域&#xff0c;推动了防伪行业的持续、健康发展。 常见的产品防伪技术有&#xff1a;隐形分子技术、二维码防伪、揭开留底防伪、安…

beego项目部署与热更新

1.开发自己的第一个项目 这里我引用的是在线聊天室&#xff0c;参考源码是https://github.com/beego/samples/tree/master/WebIM 在源码的基础上重新开发&#xff0c;整理项目发布到了liu289747235/WebIM 推荐下载源码&#xff1a;https://gitee.com/myselfyou/web-im 在线…

嵌入式Linux Qt交叉编译环境搭建

1、下载Qt编译器 TinkerBoard2主板,BuildRoot根文件系统,package自带的Qt版本为5.14.2,所以安装的版本也是5.14.2 wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-opensource-linux-x64-5.14.2.run chmod a+x qt-opensource-linux-x64-5.14.2.run ./qt-opensourc…

市场监管总局发布区块链和分布式记账技术6项标准,中创积极推动区块链产业发展!

近日&#xff0c;市场监管总局&#xff08;国家标准委&#xff09;批准发布一批重要国家标准&#xff0c;涉及生产生活、绿色可持续等多个领域&#xff0c;这些标准将在引领产业发展、促进绿色转型、助力对外贸易、推动城乡建设、提升生活品质等方面发挥重要作用。 其中一项标…

IO、NIO、IO多路复用

IO是什么&#xff1f; IO分为两类&#xff0c;它们之间是有区别的&#xff0c;而且有很大的区别&#xff1b;1. 文件系统的IO 也叫本地io&#xff0c;就是和磁盘或者外围存储设备进行读写操作&#xff0c;外围设备有USB、移动硬盘等等&#xff1b;2. 网络的IO 将数据发送给对方…

不愧是字节出来的,太厉害了...

前段时间公司缺人&#xff0c;也面了许多测试&#xff0c;一开始瞄准的就是中级水准&#xff0c;当然也没指望能来大牛&#xff0c;提供的薪资在15-25k这个范围&#xff0c;来面试的人有很多&#xff0c;但是平均水平真的让人很失望。 看了简历很多上面都是写有4年工作经验&am…

车辆导航系统行业调研:市场需求量不断发展和壮大

车辆导航系统即即车载GPS导航系统&#xff0c;其内置的GPS天线会接收到来自环绕地球的24颗GPS卫星中的至少3颗所传递的数据信息&#xff0c;结合储存在车载导航仪内的电子地图&#xff0c;通过GPS卫星信号确定的位置坐标与此相匹配&#xff0c;进行确定汽车在电子地图中的准确位…

【想要安利给所有人的开发工具】最强工具ChatGPT——分享一些使用经验

目录 &#x1f525;个人使用ChatGPT的经验 &#x1f525;如何使用ChatGPT 方法一 方法二 &#x1f525;&#x1f525;提问技巧分享 1、英语翻译员 2、面试官 3、javascript 控制台 4、Excel表格 5、作曲家 6、辩手 7、小说家 8、诗人 9、数学老师 10、网络安全…

chrony介绍和安装

chrony介绍和安装 1.chrony&#xff08;时间同步服务&#xff09; 1.1 chrony介绍 Chrony 是一个用于时间同步的软件&#xff0c;它旨在提供高精度的系统时钟同步。Chrony 软件包括一个 NTP&#xff08;Network Time Protocol&#xff0c;网络时间协议&#xff09;服务器和客…

【咕咕送书 | 第八期】羡慕同学进了大厂核心部门,看懂这本书你也能行!

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 写在前面参与规则 ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论…

Java-NIO篇章(4)——Selector选择器详解

Selector介绍 选择器&#xff08;Selector&#xff09;是什么呢&#xff1f;选择器和通道的关系又是什么&#xff1f;这里详细说明&#xff0c;假设不用选择器&#xff0c;那么一个客户端请求数据传输那就需要建立一个连接&#xff0c;为了避免线程阻塞&#xff0c;那么每个客…

万界星空科技mes系统可以为企业带来什么好处

随着信息技术的不断发展&#xff0c;MES生产制造系统的作用不断凸显。万界星空科技MES生产制造可以为企业带来四个方面的好处&#xff1a;提升生产效率、降低生产成本、优化生产过程、提高生产质量。本文将从这四个方面分别进行详细阐述&#xff0c;旨在通过对MES生产制造系统的…