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

在这里插入图片描述

文章目录

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

在 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 是一个接口,而不是一个具体的数据结构。

在这里插入图片描述

这个接口定义了四个方法: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 包确保了用户不能直接访问或修改内部状态,这是良好封装的标志。如其中的 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() 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 语言为开发者提供了一种清晰、一致且灵活的方式来管理和传递程序的状态和行为。

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

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

相关文章

【大数据分析与挖掘技术】概述

目录 一、数据挖掘简介 &#xff08;一&#xff09;数据挖掘对象 &#xff08;二&#xff09;数据挖掘流程 &#xff08;三&#xff09;数据挖掘的分析方法 &#xff08;四&#xff09;经典算法 二、Mahout &#xff08;一&#xff09;Mahout简介 &#xff08;二&#…

CVE-2023-46226 Apache iotdb远程代码执行漏洞

项目介绍 Apache IoTDB 是针对时间序列数据收集、存储与分析一体化的数据管理引擎。它具有体量轻、性能高、易使用的特点&#xff0c;完美对接 Hadoop 与 Spark 生态&#xff0c;适用于工业物联网应用中海量时间序列数据高速写入和复杂分析查询的需求。 项目地址 https://io…

【INTEL(ALTERA)】F-tile 参考时钟和系统 PLL 时钟英特尔® FPGA IP无法锁定在特定频率?

说明 由于在英特尔 Quartus Prime Pro Edition 软件 22.2 及更早版本中存在一个问题&#xff0c;您可能会观察到 F-tile 参考时钟和系统 PLL 时钟英特尔 FPGA IP无法锁定&#xff1a; 999.9 MHz&#xff0c;参考时钟频率设置为 323.2 MHz。506.88 MHz&#xff0c;参考时钟频率…

Windows系统使用手册

点击前往查看&#x1f517;我的博客文章目录 Windows系统使用手册 文章目录 Windows系统使用手册Windows10解决大小核调度问题Windows系统安装软件Windows系统Typora快捷键Windows系统压缩包方式安装redisWindows安装dockerWindows系统的docker设置阿里源Windows系统下使用doc…

Ubuntu系统pycharm以及annaconda的安装配置笔记以及问题集锦(更新中)

Ubuntu 22.04系统pycharm以及annaconda的安装配置笔记以及问题集锦 pycharm安装 安装完之后桌面上并没有生成图标 后面每次启动pycharm都要到它的安装路径下的bin文件夹下&#xff0c; cd Downloads/pycharm-2018.1.4/bin然后使用sh命令启动脚本程序来打开pycharm sh pycha…

01 MyBatisPlus快速入门

1. MyBatis-Plus快速入门 版本 3.5.31并非另起炉灶 , 而是MyBatis的增强 , 使用之前依然要导入MyBatis的依赖 , 且之前MyBatis的所有功能依然可以使用.局限性是仅限于单表操作, 对于多表仍需要手写 项目结构&#xff1a; 先导入依赖&#xff0c;比之前多了一个mybatis-plus…

动态规划汇总

作者推荐 视频算法专题 简介 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;是运筹学的一个分支&#xff0c;是求解决策过程最优化的过程。每次决策依赖于当前状态&#xff0c;又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的&#x…

《WebKit 技术内幕》之五(2): HTML解释器和DOM 模型

2.HTML 解释器 2.1 解释过程 HTML 解释器的工作就是将网络或者本地磁盘获取的 HTML 网页和资源从字节流解释成 DOM 树结构。 这一过程中&#xff0c;WebKit 内部对网页内容在各个阶段的结构表示。 WebKit 中这一过程如下&#xff1a;首先是字节流&#xff0c;经过解码之…

力扣每日一练(24-1-20)

大脑里的第一想法是排列组合&#xff0c;直接给出超级准确的最优解。 但不适用&#xff0c;hhh 只要连续的n个元素大于或者等于target就可以了 题目比自己想象的要好解决 解法是使用滑动窗口算法。这个算法的基本思想是维护一个窗口&#xff0c;使得窗口内的元素总和大于等于目…

消除游戏(寒假每日一题+模拟、优化)

题目 在一个字符串 S 中&#xff0c;如果 SiSi−1 且 Si≠Si1&#xff0c;则称 Si和 Si1 为边缘字符。 如果 Si≠Si−1 且 SiSi1&#xff0c;则 Si−1 和 Si 也称为边缘字符。 其它的字符都不是边缘字符。 对于一个给定的串 S&#xff0c;一次操作可以一次性删除该串中的所…

【c++笔记】用c++解决一系列质数问题!

质数是c语言和c中比较常见的数学问题&#xff0c;本篇文章将带你走进有关质数的一系列基础问题&#xff0c;其中包含常见的思路总结&#xff0c;本篇文章过后&#xff0c;将会持续更新c算法系列&#xff0c;感兴趣的话麻烦点个关注吧&#xff01; 希望能给您带来帮助&#xff…

STM32标准库开发—MPU6050详细介绍

MPU6050简介 3轴IMU即只有3轴陀螺仪的IMU&#xff0c;其因为只有一个3轴陀螺仪&#xff0c;所以只能感知载体roll&#xff08;滚转&#xff09;、pitch&#xff08;俯仰&#xff09;、yawl&#xff08;偏航&#xff09;共3个自由度的姿态信息。 6轴IMU在3轴IMU的基础上加装了3轴…

【Python学习】Python学习21- 正则表达式(2)

目录 【Python学习】Python学习21- 正则表达式&#xff08;2&#xff09; 前言字符串检索和替换repl 参数是一个函数参考 文章所属专区 Python学习 前言 本章节主要说明Python的正则表达式。 正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与…

嵌入式学习-网络编程-Day6

嵌入式学习-网络编程-Day6 一、思维导图 二、作业 1.基于UDP的网络聊天室&#xff08;2024.1.21号前上交&#xff09; 项目需求&#xff1a; 1.如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息 2.如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信…

认识并使用Shiro技术

认识并使用Shiro 一、对Shiro的基本认知1、Shiro是什么&#xff1f;2、Shiro的核心组件是&#xff1f;2.1 Subject2.2 UsernamePasswordToken2.3 Realm&#xff08;重点是&#xff1a;AuthorizingRealm用于授权、AuthenticatingRealm用于认证&#xff09;2.4 SecurityManager2.…

C#操作pdf之使用itext实现01-生成一个简单的table

创建.net 8控制台项目 安装itext <PackageReference Include"itext" Version"8.0.2" /><PackageReference Include"itext.bouncy-castle-adapter" Version"8.0.2" /><PackageReference Include"itext.bouncy-cast…

LabVIEW电能质量监测系统

系统利用LabVIEW开发一个基于LabVIEW的电能质量监测系统&#xff0c;模拟并监测暂态电能质量扰动&#xff0c;如电压骤升、电压骤降、电压波动和暂态振荡等。系统的硬件部分包括高精度的振动传感器和信号调节设备&#xff0c;以及型号为NI9234的数据采集卡和高性能计算机。这些…

Sqoop故障排除指南:处理错误和问题

故障排除是每位数据工程师和分析师在使用Sqoop进行数据传输时都可能遇到的关键任务。Sqoop是一个功能强大的工具&#xff0c;但在实际使用中可能会出现各种错误和问题。本文将提供一个详尽的Sqoop故障排除指南&#xff0c;涵盖常见错误、问题和解决方法&#xff0c;并提供丰富的…

卡萨帝洗衣机:被模仿也是竞争力

如何用一句话形容某家企业的竞争力和领导地位&#xff1f;“某某一出手&#xff0c;就知有没有。”这句话相当匹配。如果再加一条&#xff0c;“被模仿”也恰到好处。 从顶流公司OpenAI&#xff0c;苹果Apple Vision Pro&#xff0c;再到卡萨帝洗衣机&#xff0c;被跟随、模仿…

Mac M1 Parallels CentOS7.9 Deploy Typecho

一、创建名称空间 kubectl create ns prod二、创建PV & PVC vim local-pv1.yamlapiVersion: v1 kind: PersistentVolume metadata:name: local-pv-1 spec:capacity:storage: 1GiaccessModes:- ReadWriteOncepersistentVolumeReclaimPolicy: RetainstorageClassName: loca…