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

这篇具有很好参考价值的文章主要介绍了从 Context 看 Go 设计模式:接口、封装和并发控制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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


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

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

package main

import "context"

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // ... call cancel() when specified signals are triggered

    handle(ctx)
}

func handle(ctx context.Context) error {
    return nil
}

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

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

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

Context 的基本结构

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

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

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

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(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 看 Go 设计模式:接口、封装和并发控制,golang,设计模式

封装性

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

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

并发安全

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

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

灵活性

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

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

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

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

案例:DataStore

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

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

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

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

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

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

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

type safeDataStore struct {
	mu   sync.Mutex
	data 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

	// 使用 safeDataStore
	store = &safeDataStore{}
	store.WriteData("safe data")
	fmt.Println(store.ReadData())

	// 使用 inMemoryDataStore
	store = &inMemoryDataStore{}
	store.WriteData("in-memory data")
	fmt.Println(store.ReadData())
}

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

结论

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

到了这里,关于从 Context 看 Go 设计模式:接口、封装和并发控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • go 设计模式之观察者模式

    观察者模式定义了一系列对象之间的一对多关系。 当一个对象改变状态时,其他依赖者都会收到通知。 设计观察者模式的程序时要注意以下几点。 (1)要明确谁是观察者谁是被观察者。一般观察者与被观察者之间是多对一的关系,一个被观察者对象可以有多个观察者对象。

    2024年02月01日
    浏览(33)
  • GO设计模式——4、单例模式(创建型)

    目录 单例模式(Singleton Pattern)   优缺点 使用场景 饿汉式和懒汉式单例模式         单例模式(Singleton Pattern)是一个类 只允许创建一个 对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。单例模式的要点有三个:一是

    2024年02月05日
    浏览(47)
  • 【设计模式】使用 go 语言实现简单工厂模式

    最近在看《大话设计模式》,这本书通过对话形式讲解设计模式的使用场景,有兴趣的可以去看一下。 第一篇讲的是 简单工厂模式 ,要求输入两个数和运算符号,得到运行结果。 这个需求不难,难就难在类要怎么设计,才能达到可复用、维护性强、可拓展和灵活性高。 运

    2024年02月05日
    浏览(44)
  • Go语言并发之context标准库

    Go中的 goroutine 之间没有父与子的关系,也就没有所谓子进程退出后的通知机制,多个 goroutine 都是平行地 被调度,多个 goroutine 如何协作工作涉及通信、同步、通知和退出四个方面。 通信:chan 通道当然是 goroutine 之间通信的基础,注意这里的通信主要是指程序的数据通道。

    2024年02月09日
    浏览(38)
  • 【转存】Go语言设计模式

    导语| 设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种经过实践验证的解决方案。即使你从未遇到过这些问题,了解模式仍然非常有用,因为它能指导你如何使用面向对象的设计原则来解决各种问题,提高开发效率,降低开发成本;本文囊括了GO语言实现的

    2024年02月03日
    浏览(36)
  • [设计模式 Go实现] 结构型~享元模式

    享元模式从对象中剥离出不发生改变且多个实例需要的重复数据,独立出一个享元,使多个对象共享,从而节省内存以及减少对象数量。 flyweight.go flyweight_test.go

    2024年01月16日
    浏览(37)
  • 原型设计模式go实现尝试

    本文章尝试使用go实现“原型”。 新人设计模式理解,望大家多多指点。

    2024年02月15日
    浏览(34)
  • GO设计模式——7、适配器模式(结构型)

    目录 适配器模式(Adapter Pattern) 优缺点 使用场景 注意事项 代码实现      适配器模式(Adapter Pattern)是作为两个 不兼容 的接口之间的桥梁。将一个类的接口转化为客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    2024年02月04日
    浏览(40)
  • 抽象工厂设计模式go实现尝试

    本文章尝试使用go实现“抽象工厂”。 新人设计模式理解,望大家多多指点。

    2024年02月16日
    浏览(50)
  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

    设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。 设计模式有很多,本文主要介绍单例模式. 单例模式是一种创建型设

    2024年02月11日
    浏览(53)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包