Golang中检测 Data Race

Data Race概念 Data Race (数据竞争),是并发系统中最常见和最难调试的错误类型之一,当两个线程(协程)同时访问同一个变量并且至少其中一次访问是写入时,就会发生 Data Race 。Java 内存模型(JMM)定义了明确的 Happens-Before 规则和提供 Volatile 机制来初步保证程序的执行顺序,Go内存模型(GMM)同样也有相同的规则定义,此外 Golang 还提供了一种检测 Data Race 的机制,帮助开发者更好的分析并发代码的正确性。 go在1.1版本时支持data race 检测。 Data Race 和 Race Condition 的区别: 这两个概念非常容易混淆,stackoverflow上也有讨论这个问题,其实我认为这篇文章很好的区别了它们,所以我进行了翻译。 检测 Data Race 使用 -race 参数可以用来检测这种竞态: 1 2 3 4 $ go test -race mypkg // to test the package $ go run -race mysrc.go // to run the source file $ go build -race mycmd // to build the command $ go install -race mypkg // to install the package 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { c := make(chan int) m := make(map[string]int) go func() { m["a"] = 1 // 访问map冲突 c <- 1 }() m["a"] = 2 // 访问map冲突 <-c for k, v := range m { fmt.Printf("key = %v, val = %v\n", k, v) } } 示例程序中,两个 goroutine 同时读写 map 存在竞态,可能造成数据不正确的情况,但是错误难以发现,通过执行时添加 -race 选择可以检测: ...

2024-06-20 · 5 min · 1042 words · Hank

Golang中singleflight的实现原理

我们知道了singleflight的用法,使用 singleflight 我们可以抑制多个请求,极大地节约带宽、增加系统吞吐量、提升性能。那么,singleflight 底层是如何实现的呢?本文我们来分析一番。 整体结构 singleflight 的核心是将同时段的多个请求抑制,只有一个请求能够真正请求资源,其他请求都阻塞。上一篇提到,singleflight的公开api仅包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制 DoChan 方法: 与Do相同,只是结果返回 <-chan Result 从这些api我们大致可以知道,调用 Do 或者 DoChan 方法就是我们所述的核心“请求”,可以猜测: 对于同一个 key,首先调用的会执行真正的逻辑,方法返回之前的后续所有相同的 key 调用都会阻塞,当第一个请求返回后,阻塞的这些调用就直接使用其返回值作为自己的返回值了。 顺着上述猜想的逻辑,我们看看singleflight的源码实现。代码不多,算上注释一共就200来行,我们来一一分析。 Group struct 首先看看 Group 的代码: 1 2 3 4 type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } Group 表示处理相同数据的一系列工作,这些工作存储到一个 map[string]*call 的结构中,为了保证并发安全,Group 内部持有 sync.Mutex 锁用来保护这个 map 的读写。 Group 有一个非常重要的两个方法 Do 和 DoChan, 在上一篇已经介绍过了。 再来回顾一下 Do 方法的定义: 1 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 已经介绍过,这里再详细看看这些参数: ...

2024-06-10 · 4 min · 791 words · Hank

Golang中singleflight的用法

在开发过程中,尤其是web server,有时候我们需要并发的请求数据,对于某些数据,同时发起的多个请求其实拿到的数据都是相同的,如果不处理这类请求,那么每个请求都会获取一遍数据,这无疑是对资源的浪费。比如要从数据库查询相同 id 的一条数据,并发的多个请求都会执行一次 sql 语句,增加了数据库的压力。 有没有一种方案,当多个请求同时发起后,只有第一个请求去获取数据,在它返回之前,其他请求都各自阻塞等待直到真正执行数据获取的请求返回后,直接拿它的结果?我们把这种将同时发起的多个请求转为一个请求去执行真正业务逻辑的情况称为“请求抑制”。在 Go 中,singleflight 就是用来处理请求抑制的。 简介 singleflight 包全路径为 golang.org/x/sync/singleflight, 目前版本是 v0.7.0。 前边提到,singleflight 用于抑制同一时间获取相同数据的重复请求。当存在多个重复请求时,singleflight 保证只有一个请求能执行,其他请求阻塞,直到前边的请求返回后直接将其返回值共享(shared)给阻塞的这些请求。 在使用之前,首先要理解何为"重复请求",如何区分"相同数据"。 相同数据:指并发下当前时间段多个请求获取的数据是完全相同的,比如获取全局的配置、查询天气数据等。 重复请求:指处理相同数据时,在一个请求从发起到返回之前这段时间,又有其他多个请求发起,那么这些请求就是重复请求。 理解了这两点,现在我们来看看 singleflight 的用法。 用法 singleflight 整体设计比较简单,公开的 api包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制,后文详述 DoChan 方法: 与Do相同,只是结果返回 <-chan Result Do 方法 Do 方法表示执行请求抑制,其定义如下: 1 2 3 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { // ... } 首先,它需要在 singleflight.Group 实例下执行,用于抑制这一组请求。 两个参数: key: 这个参数用来指示"相同数据",也就是说相同的key代表相同数据,区分相同数据是正确使用 singleflight 的关键。 fn: 真正执行业务逻辑的方法,该方法返回一个任意对象 interface{} 和一个 error 返回结果: v: 就是 fn 方法返回的第一个值 err: fn 方法返回的第二值 error shared: 当抑制了其他请求,返回 true, 也就是说将真正执行业务逻辑的请求返回结果共享给其他请求后,该值为 true, 否则为 false DoChan 方法 DoChan 方法与 Do 类似,只是将结果封装为 Result 并返回它的 chan: ...

2024-05-18 · 3 min · 491 words · Hank

依赖注入库wire使用入门

依赖注入(Dependency injection)不是一个新概念了,早在2004年 Martin Fowler 就在其文章Inversion of Control Containers and the Dependency Injection pattern提出了依赖注入模式。在Java的世界中,随处可见依赖注入的使用,如Spring框架的核心之一就是控制反转(IoC),而依赖注入就是 IoC 思想的一种技术实现,比如我们常用的 Spring 的注解 @Autowire,还有 Jsr250规范定义的 @Resource 注解都实现了依赖注入。 简单而言,依赖注入就是以构建松散耦合的程序为目的、以控制反转为基本思想而在软件工程实现的一种编程技术,它使得程序不需要关注对象的创建逻辑,只是通过函数或者属性告诉程序自己需要什么对象,关注所依赖对象的使用逻辑,从而将对象的创建和使用过程分离开。可见,依赖注入技术遵循依赖倒置原则。 虽然 GO 不是面向对象的语言,但是它也有依赖注入实现,常用的依赖注入框架包括 google 自身出品的 wire、Uber的dig、Facebook的inject等等。本文将介绍 wire 的基本使用。 不使用依赖注入时 在开始介绍 wire 之前,我们来编写一个简单的程序,该程序可以创建问候者并向您问好。 我们创建一个 greeter.go 文件,它包含以下几个结构体: 1 2 3 4 5 6 7 8 9 type Message string type Greeter struct { Msg Message } type Event struct { Greeter Greeter } Message 表示消息,Greeter 表示问候者,它需要一个 Message,Event 表示一个事件,它用来触发问候。 现在,我们需要增加创建这些结构体的方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func NewMessage(name string) Message { return Message(fmt.Sprintf("hello, %s!", name)) } func NewGreeter(msg Message) Greeter { return Greeter{Msg: msg} } func NewEvent(g Greeter) Event { return Event{Greeter: g} } func (g Greeter) Greet() Message { return g.Msg } func (e Event) Start() { msg := e.Greeter.Greet() fmt.Println(msg) } 通过 NewMessage 创建消息,通过 NewGreeter 创建一个问候者,通过 NewEvent 创建事件,然后就可以调用 Start 方法来发起问候了,其实底层最终调用的是 Greeter 的 Greet 方法。 ...

2023-08-06 · 4 min · 745 words · Hank

使用cli框架开发CLI程序

Go语言编译后的程序本身就是一个可用于命令行的可执行文件,而且Go天生支持CLI程序(command line interface),这得益于Go精简的语法以及自身支持Flag来解析命令行的选项参数等。但是,基于Go原始能力开发CLI程序仍然非常繁琐,如解析参数就是一个非常麻烦的工作。幸好,有许多非常强大的库可以用来简化我们的工作: cobra: 一个非常强大的用于构建CLI程序的库,官方地址见这儿 urfave/cli: 另一个使用广泛的CLI开发库,同样足够强大且简单易上手,官方地址见这儿 survey: 一个强大的构建交互式命令行程序的库,详情见这里 在开发CLI之前,你可以阅读Go官方的构建CLI程序指南。本文介绍如何使用 urfave/cli 库开发完整的CLI程序。 CLI程序 命令行界面(CLI,command line interface) 是一种通过用户或客户端发出的命令以及设备或程序以文本行形式做出的响应与设备或计算机程序进行交互的方式。 以上是维基百科的解释,简单而言就是控制台程序,我们需要通过控台执行程序并输入程序内置支持的选项、参数等完成与程序的交互以实现功能。 一般而言,CLI都具备这些功能: 命令:一个CLI程序应该至少支持一个命令,才能用来实现功能,大多CLI都支持多个命令,而且命令下还支持多个的子命令,用来将功能细分 选项:选项分为全局选项和命令选项,全局选项表示对所有命令都可以使用的选项,而命令选项这仅对特定命令有效 参数:CLI支持用户通过控制台传入参数告诉其特定信息,一般情况会通过选项指定参数来区分不同的用途,也可以直接传递给命令 帮助:展示给用户如何使用当前程序的帮助信息 输出:程序处理完成后展示给用户的结果信息 别名:命令和选项都应该支持别名,当命令和选项太长时用来简化输入 当然,CLI还包括程序退出码、错误等信息,不再一一列举。 cli框架简介 urfave/cli 是一个简单、快速且有趣的包,用于在 Go 中构建命令行应用程序。目标是使开发人员能够以富有表现力的方式编写快速且可分发的命令行应用程序。 目前最新支持的版本是 v2,这也是目前使用最广泛、功能强大的版本。 官方使用文档: https://cli.urfave.org/v2/getting-started/ 仓库地址: https://github.com/urfave/cli 接下来,我们将创建一个CLI应用并逐步完善它。 创建应用 创建一个cli目录,然后初始化go模块: 1 2 3 $ mkdir cli $ cd cli $ go mod init 编辑 go.mod 文件,将模块名称改为 cli_demo,然后安装 urfave/cli: 1 $ go get github.com/urfave/cli/v2 新建 main.go 文件作为程序的入口,编写代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( "fmt" "github.com/urfave/cli/v2" "os" ) func main() { cliApp := cli.NewApp() cliApp.Name = "demo-cli" cliApp.Usage = "cli usage demo" cliApp.Version = "0.0.1" err := cliApp.Run(os.Args) // app退出不会调用 os.Exit,所以默认退出代码都是0,可以通过 cli.Exit方法指定退出信息和退出码 if err != nil { fmt.Printf("demo-cli execute error: %v\n", err) os.Exit(-1) } } 首先,我们使用 cli.NewApp() 创建 *cli.App 实例,然后分别设置了程序的名称、使用说明、版本,最后使用 cliApp.Run(os.Args) 方法运行程序,传入系统参数并处理错误信息。 ...

2023-07-16 · 5 min · 1024 words · Hank

(译)Go1.13中处理错误

Damien Neil 和 Jonathan Amsterdam, 2019 年 10 月 17 日, 原文地址: https://go.dev/blog/go1.13-errors 1. 介绍 在过去的十年中,Go 将 "错误作为值" 来处理 ,这对我们很有帮助。尽管标准库对错误的支持很少 —— 只有 errors.New 和 fmt.Errorf 函数,它们产生的错误只包含一条消息 —— 内置 error 接口允许 Go 程序员添加他们想要的任何信息。它所需要的只是一个实现 Error 方法的类型: type QueryError struct { Query string Err error } func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } 像这样的错误类型无处不在,它们存储的信息千差万别,从时间戳到文件名再到服务器地址。通常,该信息包括另一个较低级别的错误以提供额外的上下文。 一个错误包含另一个错误的模式在 Go 代码中非常普遍,经过 广泛讨论 后,Go 1.13 添加了对它的明确支持。这篇文章描述了提供该支持的标准库的新增内容:errors 包中的三个新函数,以及 fmt.Errorf. 在详细描述更改之前,让我们回顾一下在该语言的先前版本中如何检查和构造错误。 2. Go 1.13 之前的错误 2.1. 检查错误 Go 错误是值。程序以几种方式根据这些值做出决策。最常见的是比较错误以 nil 查 看操作是否失败。 ...

2023-05-05 · 4 min · 828 words · Hank

(译)Go错误是值

Rob Pike,2015 年 1 月 12 日 ,原文地址: https://go.dev/blog/errors-are-values Go 程序员(尤其是刚接触该语言的程序员)讨论的一个共同点是如何处理错误,这些讨论最终往往都将回归于无数次出现的代码片段: if err != nil { return err } 如上所示,但我们最近扫描了所有我们能找到的开源项目,发现这个片段每页或每两页只出现一次,比某些人认为的要少。尽管如此,许多程序员仍然认为必须键入如下代码来处理错误: if err != nil 一直以来,人们觉得一定有什么地方出了问题,而明显的目标就是 Go 本身。 这是不幸的、误导性的,而且很容易纠正。也许刚接触 Go 的程序员会问,“我该如何处理错误?”,然后学习这种模式,最后就此打住。在其他语言中,可能会使用 try-catch 块或其他类似机制来处理错误。因此,程序员认为,我在我的旧语言中可以使用 try-catch 时,但在 go 中我只能输入 if err != nil 来处理错误。随着时间的推移,Go 代码收集了许多这样的片段,结果感觉很笨拙。 不管这种解释是否成立,很明显这些 Go 程序员忽略了关于错误的一个基本观点: 错误是值。 可以对值进行编程,并且由于错误是值,因此可以对错误进行编程。 当然,涉及错误值的常见语句是测试它是否为 nil,但是错误值可以做无数其他事情,应用其中一些其他事情可以使您的程序更好,从而消除大部分样板代码,避免多次使用 if 语句检查每个错误。 一个简单示例是 bufio 包中的 Scanner。它的 Scan 方法执行底层 I/O,这当然可能导致错误。然而,该 Scan 方法根本不会暴露错误。相反,它返回一个布尔值和一个单独的方法,在扫描结束时运行,报告是否发生错误。客户端代码如下所示: scanner := bufio.NewScanner(input) for scanner.Scan() { token := scanner.Text() // process token } if err := scanner.Err(); err != nil { // process the error } ...

2023-04-30 · 2 min · 364 words · Hank

(译)Go模块:管理依赖项

原文地址: https://go.dev/doc/modules/managing-dependencies 当您的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,您可能需要升级或更换它们。Go 提供了依赖管理工具,可帮助您在合并外部依赖项时确保 Go 应用程序的安全。 本文介绍如何执行一些任务来管理您代码中的依赖项,您可以使用 Go tools 执行其中的大部分操作。本主题还介绍了如何执行其他一些您可能会觉得有用的依赖相关任务。 相关阅读 如果您不熟悉模块和依赖,请查看 入门教程 以获得简要介绍。 使用该 go 命令管理依赖项有助于确保您的需求保持一致,并且您的 go.mod 文件的内容是有效的。有关命令的参考,请参阅 go命令文档。您还可以通过键入 go help 命令名称` 从命令行获取帮助,如 go help mod tidy. 使用 Go 命令更改依赖项时会编辑 go.mod 文件。有关文件内容的更多信息,请参阅 go.mod 文件参考文档。 让您的编辑器或 IDE 能够感知 Go 模块可以让您更轻松地管理它们。有关支持 Go 的编辑器的更多信息,请参阅 编辑器插件和 IDE。 本主题不描述如何开发、发布和版本模块以供其他人使用。有关更多信息,请参阅 开发和发布模块。 1. 使用和管理依赖项的工作流程 您可以通过 Go tools 获取和使用有用的包。在 pkg.go.dev 上,您可以搜索您觉得有用的包,然后使用 go 命令将这些包导入您自己的代码中以调用它们的功能。 下面列出了最常见的依赖管理步骤: 在 pkg.go.dev上 查找有用的包。 在代码中导入所需的包。 将您的代码添加到模块以进行依赖跟踪(如果它不在模块中)。请参阅 启用依赖项跟踪 [添加外部包作为依赖项,以便您可以管理它们。 随着时间的推移,根据需要 升级或降级依赖版本。 2. 依赖项作为模块管理 在 Go 中,依赖项作为包含导入包的模块来管理。此过程由以下功能支持: 用于发布和检索模块的去中心化系统。这使得开发人员定义模块版本号并发布模块,其他开发人员就可以在自己的存储库中使用这些模块了。 包搜索引擎和文档浏览器 (pkg.go.dev),您可以在其中搜索模块。请参阅 查找和导入有用的包。 模块版本编号约定可帮助您了解模块的稳定性和向后兼容性保证。请参阅 Go模块版本编号。 Go tools 可以让您更轻松地管理依赖项,包括获取模块的源代码、升级等。有关更多信息,请参阅本文相关部分。 3. 查找和导入包 您可以在 pkg.go.dev 上搜索以查找您所需要的软件包。 ...

2023-04-19 · 3 min · 636 words · Hank

(译)Go 中的字符串、字节、符文和字符

原文地址: https://go.dev/blog/slices 作者: Rob Pike 日期: 2013 年 9 月 26 日 1. 简介 这篇文章讨论了 Go 中的字符串。起初,字符串对于一篇博文来说似乎太简单了,但要很好地使用它们,不仅需要了解它们的工作原理,还需要了解字节、字符和符文之间的区别,Unicode 和 UTF- 8、字符串和字符串字面量的区别,以及其他更细微的区别。 处理该话题的一种方法首先是回答这个问题:“当我在位置 n 检索 Go 字符串时,为什么我没有得到第 n 个字符?” 正如您将看到的,这个问题引导我们了解有关文本在现代世界中如何工作的许多细节。 2. 什么是字符串? 让我们从一些基础知识开始。 在 Go 中,字符串实际上是只读的字节切片。如果您完全不确定字节切片是什么或它是如何工作的,请阅读 数组、切片和字符串 一文。 重要的是首先要明确一个字符串包含_任意_多个字节,不论字符串是否包含 Unicode 文本、UTF-8 文本或任何其他预定义格式。就字符串的内容而言,它完全等价于一个字节切片([]byte)。 下边是一个字符串(稍后详述),它使用 \xNN 符号来定义一个字符串常量,其中包含一些特殊的字节值(字节的取值范围从十六进制值 00 到 FF)。 const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 3. 打印字符串 由于上边我们的示例字符串 sample 中的某些字节不是有效的 ASCII,甚至不是有效的 UTF-8,所以直接打印字符串会产生奇怪的输出。简单的打印语句如下: fmt.Println(sample) 产生这种乱码(其确切外观因环境而异)输出: ��=� ⌘ 为了找出 sample 字符串底层到底是什么,我们需要把它拆开检查一下。有几种方法可以做到这一点。最明显的是循环其内容并单独提取字节,如以下`for`循环所示: for i := 0; i < len(sample); i++ { fmt.Printf("%x ", sample[i]) // 输出为十六进制格式 } ...

2023-03-25 · 4 min · 682 words · Hank

(译)数组、切片和字符串 - “append” 原理

原文地址: https://go.dev/blog/slices 作者: Rob Pike 日期: 2013 年 9 月 26 日 介绍 过程编程语言最常见的特征之一是数组的概念。数组看起来很简单,但在将它们添加到语言时必须回答许多问题,例如: 固定尺寸还是可变尺寸? 大小是类型的一部分吗? 多维数组是什么样的? 空数组有意义吗? 这些问题的答案会影响数组是否只是语言的一个特性还是其设计的核心部分。 在 Go 的早期开发中,在设计感觉正确之前,花了大约一年的时间来确定这些问题的答案。关键步骤是引入 slices,它建立在固定大小的 数组 之上,以提供灵活、可扩展的数据结构。然而,直到今天,刚接触 Go 的程序员经常对切片的工作方式感到困惑,也许是因为其他语言的经验影响了他们的思维。 在这篇文章中,我们将尝试消除混淆。我们将通过构建片段来解释 append 内置函数是如何工作的,以及为什么它会以这种方式工作。 数组 数组是 Go 中的一个重要构建块,但就像建筑物的基础一样,它们通常隐藏在更可见的组件之下。在我们继续讨论更有趣、更强大、更突出的切片概念之前,我们必须简单地讨论一下它们。 数组在 Go 程序中并不常见,因为数组的大小是其类型的一部分,这限制了它的表达能力。 以下代码: var buffer [256]byte 声明了一个数组变量 buffer ,[256]byte 表示它持有的数据类型为 byte,长度为 256。如果想声明 512 个字节的数组可以这样: [512]byte。 与数组关联的数据就是 数组中的元素。上边声明的数组缓冲区在内存中看起来像这样: buffer: byte byte byte ... 256 times ... byte byte byte 也就是说,该变量只保存 256 个字节的数据,仅此而已。我们可以使用熟悉的索引语法 buffer[0]、buffer[1] 到 buffer[255] 来访问它的元素。(索引范围 0 到 255 涵盖 256 个元素)尝试使用超出此范围的索引值访问 buffer 会使程序崩溃。 ...

2023-02-09 · 8 min · 1504 words · Hank

(译)进入Go模糊测试的世界

原文地址: https://go.dev/doc/tutorial/fuzz 本教程介绍了 Go 中模糊测试的基础知识。模糊测试会针对您的测试准备一些随机数据然后运行测试时使用它们,以尝试找出漏洞或导致崩溃的输入。可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击(XSS)。 在本教程中,您将为一个简单的函数编写一个模糊测试,运行 go 命令,并调试和修复代码中的问题。 有关本教程中术语的帮助,请参阅 "词汇表"。 您将逐步完成以下部分: 为您的代码创建一个文件夹 添加代码进行测试 添加单元测试 添加模糊测试 修复无效字符串错误 修复双反错误 结论 注意 更多 Go 教程,请参阅 教程。 Go fuzzing 当前支持 Go Fuzzing 文档 中列出的内置类型的子集,并支持将来添加的更多内置类型。 1. 先决条件 Go 1.18 或更高版本的安装。 有关安装说明,请参阅 安装 Go。 用于编辑代码的工具。 您拥有的任何文本编辑器都可以正常工作。 一个命令终端。 Go 在 Linux 和 Mac 上的任何终端以及 Windows 中的 PowerShell 或 cmd 上都能很好地工作。 支持模糊测试的环境。 目前仅在 AMD64 和 ARM64 架构上使用覆盖检测进行模糊测试。 2. 为您的代码创建一个文件夹 首先,为您要编写的代码创建一个文件夹。 1、 打开命令提示符并切换到您的主目录。 在 Linux 或 Mac 上: $ cd ...

2022-12-19 · 9 min · 1738 words · Hank

(译)初始Go模糊测试

原文地址: https://go.dev/security/fuzz/ 从 Go 1.18 开始,Go 在其标准工具链中支持模糊测试。Native Go 模糊测试受 OSS-Fuzz 支持。 Go模糊测试详细教程见:进入Go模糊测试的世界 一文。 1. 概述 Fuzzing 是一种自动化测试,它不断地操纵程序的输入以查找错误。Go fuzzing 使用覆盖率指导来智能地不断重复执行模糊测试的代码,以发现并向用户报告问题。由于它可以覆盖人类经常错过的边缘情况,因此模糊测试对于发现安全漏洞特别有价值。 下面是一个 模糊测试 的例子,突出了它的主要组成部分。 上图显示整体模糊测试的示例代码,其中包含一个模糊目标( fuzz target )。 在模糊目标之前调用 f.Add 添加种子语料库,模糊目标的参数高亮显示为fuzzing参数。 2. 编写模糊测试 2.1. 要求 以下是模糊测试必须遵循的规则。 模糊测试必须是一个形如 FuzzXxx 的函数,以 Fuzz 作为前缀,它只接受一个 *testing.F 参数并且没有返回值。 模糊测试必须在 *_test.go 文件中才能运行。 调用 (testing.F).Fuzz 方法时的匿名函数参数称之为 模糊目标,形如 func(t *testing.T, xxx), 它必须是一个函数,接受一个 *testing.T 作为第一个参数,其他后续参数称为模糊参数,且该函数没有返回值。 每个模糊测试必须只有一个模糊目标。 所有 种子语料库 条目必须具有与 模糊参数 相同的类型,并且顺序相同。这适用于调用 (*testing.F).Add 添加的种子语料库和 testdata/fuzz 目录中已有的语料库文件。 模糊测试参数只能是以下类型: string, []byte int, int8, int16, int32/rune, int64 uint, uint8/byte, uint16, uint32, uint64 float32, float64 bool ...

2022-10-26 · 2 min · 426 words · Hank

GoLang教程——常量

golang 中常量设计摒弃了 C 语言中常量被诟病的问题,比如类型安全问题、定义和使用复杂、编译器替换字面值等,而是进行了简化。在 go 中,常量一旦声明则在编译期其值就已知且不可更改。 1. 常量的定义 常量的定义很简单,使用 const 关键字声明即可: // 单行声明常量 const c1 = 1 const c2 = -100 const c3 = "hello" 与变量一样,同样可以组合以简化声明: const ( c4 = 3.14 c5 = 1.2 + 12i // 复数 ) 但是需要注意,组合声明时后续的常量如果没有赋值则会重复最近一个非空表达式的值,例如: const ( m = 1 // 1 n // 1 k // 1 l = m + 1 // 2 i // 2 ) fmt.Println(m, n, k, l, i) ...

2022-09-10 · 3 min · 585 words · Hank

GoLang教程——变量

本质上,变量是一个持有一类值的数据存储空间的命名,我们声明变量其实就是在申请存储空间,之后引用变量其实就是在使用这块存储空间中的值。GoLang是一门强类型语言,每一个声明的变量都对应着一种类型,但是声明变量的方式却有多种。 1. 变量的定义 Go语言规范中对变量的定义如下: 变量是保存着值的一块存储空间,存储什么样的值由变量的类型决定。 变量的概念在各个语言中都有,本质上变量就是为一块存储空间取一个名字,以便开发者后续引用这个名字来使用存储空间中存储的值。至于存储什么样的值,这必须有变量的类型来决定,所以Go、Java这种强类型语言在变量声明时必须指定变量类型。 2. 变量的声明 上一篇中提到:变量声明过后,必须被使用,否则编译错误。Go中变量的声明有两种方式: 使用 var 声明一个变量,适用于大多数情况,可以不用赋初始值时 使用 := 声明变量并初始化它,适用于需要为变量直接赋初始值时,比如直接声明变量接收方法的返回值 第一种,使用 var 关键字声明变量: var v0 rune // 声明字符类型的变量 v0 var v1 int // 声明整型变量 v1 var v2 string // 声明字符串类型的变量 v2 v0 = 'A' // 给变量赋初始值 这种方式可以先声明变量然后赋值,也可以直接在声明时赋值,此时可以省略类型,编译器会自动推导出类型: var i1 = 1 fmt.Printf("i1 = %d\n", i1) 在声明多个变量时,可以使用如下简写的语法: var ( v3 [2]int // 数组 v4 []int // 数组切片 v5 struct{ i int } // 结构体 v6 *int // 指针 v7 map[string]int // map ) ...

2022-08-02 · 2 min · 310 words · Hank

GoLang教程——代码风格

Go作为编程语言中的后起之秀,在语言规范上吸收了诸多语言的优点,并形成了自身独特的语言风格。本文探讨一下Go语言的代码风格。 1. main方法 main 方法同样是go程序的入门函数,但是其定义非常简单,比如上一篇中的: func main() { fmt.Println("Hello, world!") } 直接用 func 关键字定义一个名为 main 的方法即可,没有任何参数。 那么,如何获取传递给 main 的参数呢,可以使用 os.Args: func main() { fmt.Println("hello go!") for i, arg := range os.Args { fmt.Println(i, "=", arg) } } os.Args 获取命令行传递的参数,第一个始终为执行程序的名称,一个示例执行结果如下: $ go run main_func.go a b hello go! 0 = /var/folders/8b/y3pklwbs1wj_cq7hm_yjwgpr0000gn/T/go-build3458283994/b001/exe/main_func 1 = a 2 = b 当然,通常情况不会使用 os.Args 来解析命令行参数,而是使用 go 标准库的 flag 包。 2. go的关键字 go的内置关键字有25个: ...

2022-07-30 · 3 min · 526 words · Hank

GoLang教程——初识Go语言

Go 语言是最近几年发展最快、最火热的编程语言之一,由 google 公司出品,其学习成本低、天生支持高并发、语法简洁高效、强大的标准库以及日益丰富的生态特细等特性使得其非常适合开发后端服务程序,并逐渐挑战着 Java 在服务端编程语言中的领导地位。 1. Go语言的诞生 Go 语言的前身被认为是一种名为 Limbo 的编程语言,它是由 Unix 之父、C语言之父 肯·汤普逊(Ken Thompson) 和 丹尼斯·里奇(Dennis Ritchie) 这两位计算机灵魂人物领衔并在一个名为 Plan 9 的的操作系统研究项目中发明的。 Go语言起源于2007年,当时还是身在 Google 的 Plan 9 项目原班人马在业余时间编写完成的,后来 Google 意识到了 Go 的巨大潜力并全力支持这个项目。Go 在2009年11月正式对外开放,并在2012年3月28日发布了第一个正式版本。 Go 语言的主要开发者有 肯·汤普逊(Ken Thompson)、罗布·派克(Rob Pike)、罗伯特·格里泽默(Robert Griesemer)等,每一位都是赫赫有名的大师级人物: 肯·汤普逊:与丹尼斯·里奇在贝尔实验室发明了Unix操作系统,以及用于该系统的 C编程语言 Figure 1. Ken Thompson (left) and Dennis Ritchie (right) 罗布·派克:Unix小组成员,参与了Unix后续的Plan 9和Inferno操作系统,同时也是Limbo语言和Go语言共同的发明者 罗伯特·格里泽默:协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8,Go语言的共同创造者 2. Go的特点 Go 始于 2007 年 9 月,当时 Robert Griesemer、Ken Thompson 和我开始讨论一种新语言,以解决我们和 Google 的同事在日常工作中面临的工程挑战。 当我们在 2009 年 11 月首次向公众发布 Go 时,我们不知道该语言是否会被广泛采用,或者它是否会影响未来的语言。回顾 2020 年,Go 在两个方面都取得了成功:它在 Google 内外广泛使用,其网络并发和软件工程方法对其他语言及其工具产生了显着影响。 ...

2022-07-21 · 3 min · 433 words · Hank

(译)Go并发模式——Context

原文地址: https://go.dev/blog/context 作者:Sameer Ajmani 时间:29 July 2014 1. 简介 [1] 在 Go 服务器中,每个传入的请求都在其自己的 goroutine 中处理。请求处理程序通常会启动额外的 goroutine 来访问数据库和 RPC 服务等后端。处理请求的一组 goroutine 通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当请求被取消或超时时,所有处理该请求的 goroutines 都应该快速退出,以便系统可以回收它们正在使用的任何资源。 在 Google,我们开发了一个 context 包,可以轻松地将请求范围的值、取消信号和截止日期等跨 API 边界传递给正在处理请求的所有 goroutine。 该软件包 作为context公开可用 。本文介绍了如何使用该包并提供了一个完整的工作示例。 2. Context context 包的核心是 Context 类型: type Context interface { (1) Done() <-chan struct{} (2) Err() error (3) Deadline() (deadline time.Time, ok bool) (4) Value(key interface{}) interface{} (5) } 1 Context 携带截止日期、取消信号和请求范围的跨越 API 边界的值,多个 goroutine 同时使用它的方法是安全的。 2 Done 返回一个在此 Context 取消或超时时的通道(chan) 3 Err 错误信息说明 context 为什么被取消, 在 Done 返回的 chan 被关闭之后获取 4 Deadline 返回 Context 被取消的时间 5 Value 返回参数 key 关联的值,没有则返回 nil ...

2022-07-19 · 5 min · 887 words · Hank