从 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.mzph.cn/news/635962.shtml

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

相关文章

智慧工地解决方案及案例:PPT全文26页,附下载

关键词&#xff1a;智慧工地解决方案&#xff0c;智慧工地建设&#xff0c;智慧工地整体架构&#xff0c;数字化工地&#xff0c;智慧工程 一、智慧工地建设对传统建筑业的影响 1、提高了施工效率和质量&#xff1a;智慧工地建设采用了先进的信息技术和管理方法&#xff0c;可…

力扣343. 整数拆分(动态规划)

Problem: 343. 整数拆分 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 该题目可以抽象成动态规划中的爬楼梯模型&#xff0c;将整数的拆分类比为上台阶&#xff1a; 1.每个阶段可以从整数中划分出1、2、…k的一个整数 2.int dp[n 1] dp[i]表示为i的整数划分的最大…

Next-GPT: Any-to-Any Multimodal LLM

Next-GPT: Any-to-Any Multimodal LLM 最近在调研一些多模态大模型相关的论文&#xff0c;发现Arxiv上出的论文根本看不过来&#xff0c;遂决定开辟一个新坑《一页PPT说清一篇论文》。自己在读论文的过程中会用一页PPT梳理其脉络和重点信息&#xff0c;旨在帮助自己和读者快速了…

线程池的简单介绍及使用

线程池 线程池的参数介绍拒绝策略 线程池的任务处理流程使用Executors创建常见的线程池 线程池的参数介绍 corePoolSize: (核心线程数)这是线程池中始终存在的线程数&#xff0c;即使这些线程处于空闲状态。maximumPoolSize:(最大线程数) 是线程池允许的最大线程数。keepAliveT…

免费200万Tokens 用科大讯飞API调用星火大模型服务

简介 自ChatGPT火了之后&#xff0c;国内的大模型发展如雨后春笋。其中的佼佼者之一就是科大讯飞研发的星火大模型,现在大模型已经更新到V3 版本&#xff0c;而且对开发者也是相当友好&#xff0c;注册就送200万tokens,讯飞1tokens 约等于 1.5 个中文汉字 或者 0.8 个英文单词…

安卓开发-day

一、安卓项目结构 1、manifests文件夹 Android系统配置文件夹&#xff0c;包含一个AndroidManifest.xml文件&#xff1b; AndroidMainifest.xml文件是每个android项目必须要包含的文件&#xff08;项目唯一&#xff09;&#xff0c;创建项目时默认就会生成这个文件&#xff0…

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

目录 【Python学习】Python学习21- 正则表达式&#xff08;1&#xff09; 前言re.match函数实例 re.search方法re.match与re.search的区别参考 文章所属专区 Python学习 前言 本章节主要说明Python的正则表达式。 正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的…

HarmonyOS 页面跳转控制整个界面的转场动画

好 本文 我们来说 页面间的转场动画 就是 第一个界面到另一个界面 第一个界面的退场和第二个界面的进场效果 首先 我这里 创建了两个页面文件 Index.ets和AppView.ets index组件 编写代码如下 import router from "ohos.router" Entry Component struct Index {b…

解决你所有ArcGIS标注问题!二分式、三分式、条件分类标注、上下标、牵引线、文字格式化…

解决你所有ArcGIS标注问题&#xff01;二分式、三分式、条件分类标注、上下标、牵引线、文字格式化… 标注是将描述性文本放置在地图中的要素上或要素旁的过程。 本文整理了ArcGIS中的各种标注方法、可能遇到的问题和细节&#xff0c;内容比较杂&#xff0c;想到哪写到哪。 …

tui.calender日历创建、删除、编辑事件、自定义样式

全是坑&#x1f573;&#xff01;全是坑&#x1f573;&#xff01;全是坑&#x1f573;&#xff01;能不用就不用&#xff01; 官方文档&#xff1a;https://github.com/nhn/tui.calendar/blob/main/docs/en/apis/calendar.md 实例的一些方法&#xff0c;比如创建、删除、修改、…

python使用jupyter记笔记

目录 一、安装 二、运行jupyter 三、使用 四、记笔记 Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。 Jupyter Notebook 的本质是一个 Web 应用程序&#xff0c;便于创建和共享程序文档&a…

位运算的奇技淫巧

常见位运算总结&#xff1a; 1、基础位运算 左移<<运算 将二进制数向左移位操作&#xff0c;高位溢出则丢弃&#xff0c;低位补0。 右移>>运算 右移位运算中&#xff0c;无符号数和有符号数的运算并不相同。对于无符号数&#xff0c;右移之后高位补0&#xff…

Python使用pyechart分析疫情确诊人数图(2024)

import json from pyecharts.charts import Map from pyecharts import options as opts# 首先打开文件获取数据 f open("/Desktop/python/Project/数据可视化/疫情.txt", "r", encoding"UTF-8") data f.read()# 字符串转化成json数据 data_js…

Java多线程--创建多线程的基本方式一:继承Thread类

文章目录 一、创建和启动线程&#xff08;1&#xff09;概述&#xff08;2&#xff09;方式1&#xff1a;继承Thread类1、使用步骤2、举例2.1 案例12.2 案例22.3 案例3 3、两个问题3.1 问题13.2 问题2 4、代码及总结 二、练习&#xff08;1&#xff09;方式一&#xff08;2&…

索引的概述和性能分析

索引index&#xff0c;是一种有序的数据结构&#xff0c;可以高效的获取数据&#xff0c;在数据库中维护着满足查找特定算法的数据结构&#xff0c;就是索引 无索引的情况&#xff0c;查询数据时会全表扫描&#xff0c;效率极低 索引结构 &#xff08;1&#xff09;二叉树&…

python解决求二叉树的最长同值路径问题

对于给定的一颗二叉树&#xff0c;需要找到最长的路径&#xff0c;并且该路径上的每个节点具有相同的值的问题&#xff0c;对于寻找到的这条路径可以经过根节点也可以不经过根节点&#xff0c;两个节点之间的路径长度是由他们的变数来表示的&#xff0c;给定如下图的二叉树 添加…

【 Qt 快速上手】-②- Qt 环境搭建

文章目录 1. Qt 开发工具概述1.1 Qt Creator 介绍1.2 Visual Studio 介绍1.3 Eclipse 介绍 2. Qt SDK 的下载与安装2.1 Qt SDK 的下载2.2 Qt SDK 的安装2.3 验证 Qt SDK 安装是否成功2.4 Qt 环境变量配置 1. Qt 开发工具概述 Qt 开发环境需要安装三个部分&#xff1a; C编译器…

小伙频繁发朋友圈引发分手,拼命“晒“生活是否成为一种病态行为?

根据《西湖之声》1月19日的报道&#xff0c;一个小伙子因为一天发十几条朋友圈而引起了他女友的不满&#xff0c;女友甚至提出了分手。现如今&#xff0c;朋友圈已经成为每个人自我形象展示的平台&#xff0c;认真发朋友圈不仅是表达自己的一种方式&#xff0c;也是一种自我释放…

redis缓存和本地缓存的应用设计

数据查询顺序 一级缓存&#xff1a;本地缓存 -》二级缓存&#xff1a;redis缓存 -》数据库 本地缓存和分布式缓存 本地缓存&#xff1a;基于jvm, 意思是程序放在哪&#xff0c;数据就存储在哪&#xff0c;不需要网络请求&#xff0c;特别快&#xff0c;但是需要占用jvm的内存…

Python利用Excel读取和存储测试数据完成接口自动化教程

http_request2.py用于发起http请求 #读取多条测试用例#1、导入requests模块import requests#从 class_12_19.do_excel1导入read_data函数from do_excel2 import read_datafrom do_excel2 import write_datafrom do_excel2 import count_case#定义http请求函数COOKIENonedef ht…