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

news2024/9/24 5:31:30

在这里插入图片描述

文章目录

    • 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 语言为开发者提供了一种清晰、一致且灵活的方式来管理和传递程序的状态和行为。

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

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

相关文章

Qt 5.15.2 (MSVC 2019)编译 QWT 6.2.0 : 编译MingW或MSVC遇到的坑

MingW下编译QWt 6.2.0 下载qwt最新版本&#xff0c;用git工具 git clone下载源码 git clone https://git.code.sf.net/p/qwt/git qwt-git 或者使用我下载的 qwt 2.6.0 链接&#xff1a;https://pan.baidu.com/s/1KZI-L10N90TJobeqqPYBqw?pwdpq1o 提取码&#xff1a;pq1o 下载…

2024最新Java高频面试题总结(附答案PDF)春招面试必备!

《Java面试全解析》1000道 面试题大全详解 本人是 2009 年参加编程工作的&#xff0c;一路上在技术公司摸爬滚打&#xff0c;前几年一直在上海&#xff0c;待过的公司有 360 和游久游戏&#xff0c;因为自己家庭的原因&#xff0c;放弃了阿里钉钉团队的 offer 回到了西安。 从…

个人实现的QT拼图游戏(开源),QT拖拽事件详解

文章目录 效果图引言玩法 拖拽概念基本概念如何在Qt中使用拖放注意事项 游戏关键问题总结 效果图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c6dd66befd314442adf07e1dec0d550c.png 引言 在学习QT demo时&#xff0c;发现有一个拼图demo&#xff0c;介绍拖…

小白进阶之字符串处理

#include <cstdio> #include <cstring> int main() {char str[105];int count0,len0;scanf("%s",str);//输入字符lenstrlen(str);//求字符长for(int i0;i<len;i){if(str[i]A)//匹配计数count;}printf("%d",count); }#include <cstdio>…

【算法实验5】

实验5-1 最短下降路径问题 【样例输入】 3 2 1 3 6 5 4 7 8 9 【样例输出】 13 #include<bits/stdc.h> using namespace std; const int N 105; int n; int g[N][N]; int f[N][N]; int res 1e95; int main() {cin>>n;for(int i 1; i < n; i )for(int …

2.C语言——控制语句

控制语句 1.分支语句/判断语句if 语句if...else 语句if...else if...else语句 switch语句 2.循环语句 while 语句 do...while 语句 for 语句 3.转向语句 break continue go to 1.分支语句/判断语句 if 语句 if(boolean_expression) { /* 如果布尔表达式为真将执行的语句 */ } …

禅道-项目从0到1的过程18开源版本使用

目录 一、禅道的下载安装12版本开源版 1.1、禅道 在计算机网络行业&#xff0c;禅道是一种开源的项目管理软件。它提供了敏捷开发、测试管理、bug跟踪、需求管理、团队协作等功能&#xff0c;适用于敏捷开发团队和软件开发项目管理。禅道软件旨在帮助团队更好地管理项目、提高…

读元宇宙改变一切笔记10_支付方式

1. 元宇宙中的经济 1.1. 元宇宙被设想为一个平行世界&#xff0c;人们将在那里花大量的时间工作和生活 1.1.1. 元宇宙的实现程度部分取决于它是否建立在一个繁荣的经济体系之上 1.2. 元宇宙中的经济将大体遵循现实世界的经济模式 1.2.1. 激烈的竞争、大量营利性企业的存在、…

yolov8的目标检测、实例分割、关节点估计的原理解析

1 YOLO时间线 这里简单列下yolo的发展时间线&#xff0c;对每个版本的提出有个时间概念。 2 yolov8 的简介 工程链接&#xff1a;https://github.com/ultralytics/ultralytics 2.1 yolov8的特点 采用了anchor free方式&#xff0c;去除了先验设置可能不佳带来的影响借鉴General…

树莓派也可以部署基于YOLO的目标检测

YOLO目标检测结果 在本文的第一部分中&#xff0c;我测试了YOLO&#xff08;You Only Look Once&#xff09;这一流行的目标检测库的“复古”版本。只使用OpenCV运行深度学习模型&#xff0c;而不使用“沉重”的框架如PyTorch或Keras&#xff0c;对于低功耗设备来说是有前途的&…

Python | 六、哈希表 Hash Table(列表、集合、映射)

哈希表基础 哈希表是一类数据结构&#xff08;哈希表包含数组、集合和映射&#xff0c;和前两篇文章叙述的字符串、链表平级&#xff09;哈希表概念&#xff1a;类似于Python里的字典类型&#xff0c;哈希表把关键码key值通过哈希函数来和哈希表上的索引对应起来&#xff0c;之…

DNA序列修正*

题目 import java.util.HashMap; import java.util.Map; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();sc.nextLine();char[] sq1 sc.next().toCharArray();sc.nextLine(…

ROS第 10 课 服务数据的自定义与使用

文章目录 第 10 课 服务数据的自定义与使用1.自定义服务数据2.服务数据的使用2.1 创建服务器和客户端代码2.2 运行服务器和客户端节点 第 10 课 服务数据的自定义与使用 1.自定义服务数据 注意&#xff1a;在自定义服务数据之前&#xff0c;需要先创建工作空间和功能包&#x…

【Linux第二课-权限】操作系统、Linux用户、Linux权限、Linux文件类型、粘滞位

目录 操作系统shell外壳为什么有shell外壳shell外壳是什么shell外壳工作原理 Linux用户root用户与非root用户root用户与普通用户的切换普通用户 --> root用户root用户 --> 普通用户普通用户 --> 普通用户对一条指令提升为root权限进行执行 Linux权限Linux中的权限角色…

STM32(--001) Win10、Win11 上的驱动安装说明

一、USB线插到 CMSIS-DAP 接口上&#xff0c;将自动识别到两个设备 ① CMSIS-DAP&#xff1a;用于烧录代码、在线硬件仿真; 在Keil里烧录&#xff0c;无需通过FlyMCU; ② USB转TTL&#xff1a;用于开发板与电脑间串口通信 &#xff0c;即USART1, TX-PA9、RX-PA10; 接口备注&a…

Java 流程控制 - 分支、循环

顺序控制 程序从上到下逐行执行&#xff0c;中间没有任何判断和跳转。 public class Test{//正确形式int a 1;int b a;// 错误形式int c d 1;int d 2; }块作用域 块&#xff08;即复合语句&#xff09;是指由一对大括号括起来的若干条简单的 Java 语句。块确定了变量的…

JVM性能调优-垃圾收集器G1详解

目录 G1收集器(-XX:UseG1GC) G1垃圾收集分类 YoungGC MixedGC Full GC G1收集器参数设置 G1垃圾收集器优化建议 什么场景适合使用G1 G1收集器(-XX:UseG1GC) G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足…

redis未授权访问全漏洞复现

redis未授权访问全漏洞复现 Redis 有关的漏洞具有明显的时间分段性&#xff0c;在15年11月之前&#xff0c;主要是未授权导致的数据泄露&#xff0c;获得一些账号密码。另外还可以 DoS&#xff08;参考&#xff1a;Sangfor VMP redis unauthorized access vulnerability&#…

UI开发布局-HarmonyOS应用UI开发布局

UI页面的构建不用再像Android开发过程中在.xml文件中书写&#xff0c;可直接在页面上使用声明式UI的方式按照布局进行排列&#xff0c;构建应用的页面。 如下代码使用Row、Column构建一个页面布局&#xff0c;在页面布局中添加组件Text、Button&#xff0c;共同构成页面&#…

Git学习笔记(第5章):Git团队协作机制

目录 5.1 团队内协作 5.2 跨团队协作 Git进行版本控制都是在本地库操作的。若想使用Git进行团队协作&#xff0c;就必须借助代码托管中心。 5.1 团队内协作 问题引入&#xff1a;成员1&#xff08;大佬&#xff09;利用Git在宿主机上初始化本地库&#xff0c;完成代码的整体…