Go语言
文章平均质量分 90
Go语言
_Rye_
左手代码右手诗
一行代码一行诗
展开
-
完整版思考题答案
答:狭义上讲是不可以的,但是广义上讲是可以的。这需要一些定制化的工作,并且被给定的参数值只能是序列化的。具体可参见flag代码包文档中的例子。答:这会产生冲突。因为代表两个代码包的标识符重复了,都是flag。原创 2024-01-12 00:01:31 · 922 阅读 · 0 评论 -
49 | 程序性能分析基础(下)
这两篇文章中,我们主要讲了 Go 程序的性能分析,提到的很多内容都是你必备的知识和技巧。这些有助于你真正地理解以采样、收集、输出为代表的一系列操作步骤。我提到的几种概要信息有关的问题。需要记住的是,每一种概要信息都代表了什么,它们分别都包含了什么样的内容。还需要知道获取它们的正确方式,包括怎样启动和停止采样、怎样设定采样频率,以及怎样控制输出内容的格式和详细程度。此外,runtime/pprof包中的Lookup函数的正确调用方式也很重要。原创 2024-01-11 17:49:41 · 939 阅读 · 0 评论 -
48 | 程序性能分析基础(上)
我们这两篇内容讲的是 Go 程序的性能分析,这其中的内容都是你从事这项任务必备的一些知识和技巧。首先,我们需要知道,与程序性能分析有关的 API 主要存在于runtime、runtime/pprof和net/http/pprof这几个代码包中。它们可以帮助我们收集相应的性能概要信息,并把这些信息输出到我们指定的地方。Go 语言的运行时系统会根据要求对程序的相关指标进行多次采样,并对采样的结果进行组织和整理,最后形成一份完整的性能分析报告。这份报告就是我们一直在说的概要信息的汇总。原创 2024-01-11 17:15:40 · 720 阅读 · 0 评论 -
47 | 基于HTTP协议的网络服务
今天,我们主要讲的是基于 HTTP 协议的网络服务,侧重点仍然在客户端。我们在讨论了http.Get函数和http.Client类型的简单使用方式之后,把目光聚焦在了后者的Transport字段。这个字段代表着单次 HTTP 事务的操作过程。它是http.RoundTripper接口类型的。它的缺省值由http.DefaultTransport变量代表,其实际类型是*http.Transport。http.Transport包含的字段非常多。原创 2024-01-11 16:42:57 · 1002 阅读 · 0 评论 -
46 | 访问网络服务
我们今天提及了使用 Go 语言进行网络编程这个主题。作为引子,先介绍了关于 socket 的一些基础知识。socket 常被翻译为套接字,它是一种 IPC 方法。IPC 可以被翻译为进程间通信,它主要定义了多个进程之间相互通信的方法。Socket 是 IPC 方法中最为通用和灵活的一种。与其他的方法不同,利用 socket 进行通信的进程可以不局限在同一台计算机当中。只要通信的双方能够通过计算机的网卡端口,以及网络进行互联就可以使用 socket,无论它们存在于世界上的哪个角落。原创 2024-01-11 15:48:33 · 891 阅读 · 0 评论 -
45 | 使用os包中的API (下)
为了聚焦于os.File类型本身,我在这两篇文章中主要讲述了怎样把 os.File 类型应用于常规的文件。该类型的指针类型实现了很多io包中的接口,因此它的具体功用也就可以不言自明了。通过该类型的值,我们不但可以对文件进行各种读取、写入、关闭等操作,还可以设定下一次读取或写入时的起始索引位置。在使用这个类型的值之前,我们必须先要创建它。所以,我为你重点介绍了几个可以创建,并获得此类型值的函数。包括:os.Create、os.NewFile、os.Open和os.OpenFile。原创 2024-01-11 15:09:45 · 938 阅读 · 0 评论 -
44 | 使用os包中的API (上)
我们今天讲的是os代码包以及其中的程序实体。我们首先讨论了os包存在的意义,和它的主要用途。代码包中所包含的 API,都是对操作系统的某方面功能的高层次抽象,这使得我们可以通过它以统一的方式,操纵不同的操作系统,并得到相似的结果。在这个代码包中,操纵文件系统的 API 最为丰富,最有代表性的就是数据类型os.File。os.File类型不但可以代表操作系统中的文件,还可以代表很多其他的东西。尤其是在类 Unix 的操作系统中,它几乎可以代表一切可以操纵的软件和硬件。原创 2024-01-11 14:19:54 · 825 阅读 · 0 评论 -
43 | bufio包中的数据类型(下)
我们用比较长的篇幅介绍了bufio包中的数据类型,其中的重点是bufio.Reader类型。bufio.Reader类型代表的是携带缓冲区的读取器。它的值在被初始化的时候需要接受一个底层的读取器,后者的类型必须是io.Reader接口的实现。Reader值中的缓冲区其实就是一个数据存储中介,它介于底层读取器与读取方法及其调用方之间。此类值的读取方法一般都会先从该值的缓冲区中读取数据,同时在必要的时候预先从其底层读取器那里读出一部分数据,并填充到缓冲区中以备后用。原创 2024-01-11 10:29:51 · 797 阅读 · 0 评论 -
42 | bufio包中的数据类型 (上)
今天我们从“bufio.Reader类型值中的缓冲区起着怎样的作用”这道问题入手,介绍了一部分 bufio 包中的数据类型,在下一次的分享中,会沿着这个问题继续展开。原创 2024-01-11 09:15:50 · 769 阅读 · 0 评论 -
41 | io包中的接口和工具 (下)
我们来总结一下这两篇的内容。在 Go 语言中,对接口的扩展是通过接口类型之间的嵌入来实现的,这也常被叫做接口的组合。而io代码包恰恰就可以作为接口扩展的一个标杆,它可以成为我们运用这种技巧时的一个参考标准。在本文中,我根据接口定义的方法的数量以及是否有接口嵌入,把io包中的接口分为了简单接口和扩展接口。同时,我又根据这些简单接口的扩展接口和实现类型的数量级,把它们分为了核心接口和非核心接口。原创 2024-01-10 18:35:44 · 898 阅读 · 0 评论 -
40 | io包中的接口和工具 (上)
我们今天一直在讨论和梳理io代码包中的程序实体,尤其是那些重要的接口及其实现类型。io包中的接口对于 Go 语言的标准库和很多第三方库而言,都起着举足轻重的作用。其中最核心的io.Reader接口和io.Writer接口,是很多接口的扩展对象或设计源泉。我们下一节会继续讲解io包中的接口内容。原创 2024-01-10 17:39:17 · 836 阅读 · 0 评论 -
39 | bytes包与字节串操作(下)
我们结合两篇内容总结一下。与strings.Builder类型不同,bytes.Buffer不但可以拼接、截断其中的字节序列,以各种形式导出其中的内容,还可以顺序地读取其中的子序列。bytes.Buffer类型使用字节切片作为其内容容器,并且会用一个字段实时地记录已读字节的计数。虽然我们无法直接计算出这个已读计数,但是由于它在Buffer值中起到的作用非常关键,所以我们很有必要去理解它。无论是读取、写入、截断、导出还是重置,已读计数都是功能实现中的重要一环。原创 2024-01-10 16:30:31 · 897 阅读 · 0 评论 -
38 | bytes包与字节串操作(上)
总结一下,bytes.Buffer是一个集读、写功能于一身的数据类型。它非常适合作为字节序列的缓冲区。我们会在下一篇文章中继续对 bytes.Buffer 的知识进行延展。原创 2024-01-10 15:30:24 · 937 阅读 · 0 评论 -
37 | strings包与字符串操作
今天,我们主要讨论了strings代码包中的两个重要类型,即:Builder和Reader。前者用于构建字符串,而后者则用于读取字符串。与string值相比,Builder值的优势主要体现在字符串拼接方面。它可以在保证已存在的内容不变的前提下,拼接更多的内容,并且会在拼接的过程中,尽量减少内存分配和内容拷贝的次数。不过,这类值在使用上也是有约束的。它在被真正使用之后就不能再被复制了,否则就会引发 panic。虽然这个约束很严格,但是也可以带来一定的好处。它可以有效地避免一些操作冲突。原创 2024-01-10 14:29:52 · 772 阅读 · 0 评论 -
36 | unicode与字符编码
基于混合线程的并发编程模型自然不必多说。在数据类型方面有:基于底层数组的切片;用来传递数据的通道;作为一等类型的函数;可实现面向对象的结构体;能无侵入实现的接口等。在语法方面有:异步编程神器go语句;函数的最后关卡defer语句;可做类型判断的switch语句;多通道操作利器select语句;非常有特色的异常处理函数panic和recover。除了这些,我们还一起讨论了测试 Go 程序的主要方式。这涉及了 Go 语言自带的程序测试套件,相关的概念和工具包括:独立的测试源码文件;原创 2024-01-10 11:07:45 · 942 阅读 · 0 评论 -
35 | 并发安全字典sync.Map (下)
这两篇文章中,我们讨论了sync.Map类型,并谈到了怎样保证并发安全字典中的键和值的类型正确性。为了进一步明确并发安全字典中键值的实际类型,这里大致有两种方案可选。其中一种方案是,在编码时就完全确定键和值的类型,然后利用 Go 语言的编译器帮我们做检查。另一种方案是,接受动态的类型设置,并在程序运行的时候通过反射操作进行检查。这两种方案各有利弊,前一种方案在扩展性方面有所欠缺,而后一种方案通常会影响到程序的性能。在实际使用的时候,我们一般都需要通过客观的测试来帮助决策。原创 2024-01-10 09:42:57 · 868 阅读 · 0 评论 -
34 | 并发安全字典sync.Map (上)
我们今天讨论的是sync.Map类型,它是一种并发安全的字典。它提供了一些常用的键、值存取操作方法,并保证了这些操作的并发安全。同时,它还保证了存、取、删等操作的常数级执行时间。与原生的字典相同,并发安全字典对键的类型也是有要求的。它们同样不能是函数类型、字典类型和切片类型。另外,由于并发安全字典提供的方法涉及的键和值的类型都是interface{},所以我们在调用这些方法的时候,往往还需要对键和值的实际类型进行检查。这里大致有两个方案。原创 2024-01-09 17:48:05 · 777 阅读 · 0 评论 -
33 | 临时对象池sync.Pool
今天,我们一起讨论了另一个比较有用的同步工具——sync.Pool类型,它的值被我称为临时对象池。临时对象池有一个New字段,我们在初始化这个池的时候最好给定它。临时对象池还拥有两个方法,即:Put和Get,它们分别被用于向池中存放临时对象,和从池中获取临时对象。临时对象池中存储的每一个值都应该是独立的、平等的和可重用的。我们应该既不用关心从池中拿到的是哪一个值,也不用在意这个值是否已经被使用过。要完全做到这两点,可能会需要我们额外地写一些代码。原创 2024-01-09 16:35:06 · 940 阅读 · 0 评论 -
32 | context.Context类型
我们今天主要讨论的是context包中的函数和Context类型。该包中的函数都是用于产生新的Context类型值的。Context类型是一个可以帮助我们实现多 goroutine 协作流程的同步工具。不但如此,我们还可以通过此类型的值传达撤销信号或传递数据。Context类型的实际值大体上分为三种,即:根Context值、可撤销的Context值和含数据的Context值。所有的Context值共同构成了一颗上下文树。这棵树的作用域是全局的,而根Context值就是这棵树的根。原创 2024-01-09 15:19:53 · 792 阅读 · 0 评论 -
31 | sync.WaitGroup和sync.Once
sync代码包的WaitGroup类型和Once类型都是非常易用的同步工具。它们都是开箱即用和并发安全的。利用WaitGroup值,我们可以很方便地实现一对多的 goroutine 协作流程,即:一个分发子任务的 goroutine,和多个执行子任务的 goroutine,共同来完成一个较大的任务。在使用WaitGroup值的时候,我们一定要注意,千万不要让其中的计数器的值小于0,否则就会引发 panic。另外,原创 2024-01-09 09:06:31 · 880 阅读 · 0 评论 -
30 | 原子操作(下)
我们把这两篇文章一起总结一下。相对于原子操作函数,原子值类型的优势很明显,但它的使用规则也更多一些。首先,在首次真正使用后,原子值就不应该再被复制了。其次,原子值的Store方法对其参数值(也就是被存储值)有两个强制的约束。一个约束是,参数值不能为nil。另一个约束是,参数值的类型不能与首个被存储值的类型不同。也就是说,一旦一个原子值存储了某个类型的值,那它以后就只能存储这个类型的值了。原创 2024-01-08 17:22:08 · 836 阅读 · 0 评论 -
29 | 原子操作(上)
今天,我们一起学习了sync/atomic代码包中提供的原子操作函数和原子值类型。原子操作函数使用起来都非常简单,但也有一些细节需要我们注意。在主问题的衍生问题中对它们进行了逐一说明。原创 2024-01-08 15:56:26 · 841 阅读 · 0 评论 -
28 | 条件变量sync.Cond (下)
我们今天主要讲了条件变量,它是基于互斥锁的一种同步工具。在 Go 语言中,我们需要用sync.NewCond函数来初始化一个sync.Cond类型的条件变量。sync.NewCond函数需要一个sync.Locker类型的参数值。*sync.Mutex类型的值以及*sync.RWMutex类型的值都可以满足这个要求。另外,后者的RLocker方法可以返回这个值中的读锁,也同样可以作为sync.NewCond函数的参数值,如此就可以生成与读写锁中的读锁对应的条件变量了。原创 2024-01-08 14:57:37 · 1130 阅读 · 0 评论 -
27 | 条件变量sync.Cond (上)
我们这两期的文章会围绕条件变量的内容展开,条件变量是基于互斥锁的一种同步工具,它必须有互斥锁的支撑才能发挥作用。条件变量可以协调那些想要访问共享资源的线程。当共享资源的状态发生变化时,它可以被用来通知被互斥锁阻塞的线程。我在文章举了一个两人访问信箱的例子,并用代码实现了这个过程。原创 2024-01-08 11:55:19 · 788 阅读 · 0 评论 -
26 | sync.Mutex与sync.RWMutex
我们今天讨论了很多与多线程、共享资源以及同步有关的知识。其中涉及了不少重要的并发编程概念,比如,竞态条件、临界区、互斥量、死锁等。虽然 Go 语言是以“用通讯的方式共享数据”为亮点的,但是它依然提供了一些易用的同步工具。其中,互斥锁是我们最常用到的一个。互斥锁常常被用来:保证多个 goroutine 并发地访问同一个共享资源时的完全串行,这是通过保护针对此共享资源的一个临界区,或一组相关临界区实现的。因此,我们可以把它看做是 goroutine 进入相关临界区时,必须拿到的访问令牌。原创 2024-01-08 09:10:09 · 1097 阅读 · 0 评论 -
25 | 更多的测试手法
在本篇文章中,假设已经理解了上一篇文章涉及的内容。因此,在这里围绕着几个可以被go test命令接受的重要标记,进一步地阐释了功能测试和性能测试在不同条件下的测试流程。其中,比较重要的有最大 P 数量的含义,-cpu标记的作用及其对测试流程的影响,针对性能测试函数的探索式执行的意义,测试函数执行时间的计算方法,以及-count标记的用途和适用场景。当然了,学会怎样并发地执行多个功能测试函数也是很有必要的。这需要联合运用-parallel标记和功能测试函数中的t.Parallel方法。原创 2024-01-05 18:49:53 · 802 阅读 · 0 评论 -
24 | 测试的基本规则和流程(下)
注意,对于功能测试和性能测试,命令执行测试流程的方式会有些不同。另外一个重要的问题是,我们在与go test命令交互时,怎样解读它提供给我们的信息。只有解读正确,才能知道测试的成功与否,失败的具体原因以及严重程度等等。除此之外,对于性能测试,还需要关注命令输出的计算资源使用提示,以及各种性能度量。这两篇的文章中,我们一起学习了不少东西,但是其实还不够。我们只是探讨了go test命令以及testing包的基本使用方式。在下一篇,我们还会讨论更高级的内容。原创 2024-01-05 16:45:44 · 940 阅读 · 0 评论 -
23 | 测试的基本规则和流程 (上)
在本篇文章的一开始,我就试图向你阐释程序测试的重要性。在我经历的公司中起码有一半都不重视程序测试,或者说没有精力去做程序测试。尤其是中小型的公司,他们往往完全依靠软件质量保障团队,甚至真正的用户去帮他们测试。在这些情况下,软件错误或缺陷的发现、反馈和修复的周期通常会很长,成本也会很大,也许还会造成很不好的影响。Go 语言是一门很重视程序测试的编程语言,它不但自带了testing包,还有专用于程序测试的命令go test。我们要想真正用好一个工具,就需要先了解它的核心逻辑。原创 2024-01-05 15:44:22 · 899 阅读 · 0 评论 -
22 | panic函数、recover函数以及defer语句(下)
我们这两期的内容主要讲了两个函数和一条语句。recover函数专用于恢复 panic,并且调用即恢复。它在被调用时会返回一个空接口类型的结果值。如果在调用它时并没有 panic 发生,那么这个结果值就会是nil。而如果被恢复的 panic 是我们通过调用panic函数引发的,那么它返回的结果值就会是我们传给panic函数参数值的副本。对recover函数的调用只有在defer语句中才能真正起作用。defer语句是被用来延迟执行代码的。原创 2024-01-05 14:42:34 · 867 阅读 · 0 评论 -
21 | panic函数、recover函数以及defer语句 (上)
最近的两篇文章,我们是围绕着 panic 函数、recover 函数以及 defer 语句进行的。今天主要讲了 panic 函数。这个函数是专门被用来引发 panic 的。panic 也可以被称为运行时恐慌,它是一种只能在程序运行期间抛出的程序异常。Go 语言的运行时系统可能会在程序出现严重错误时自动地抛出 panic,我们在需要时也可以通过调用panic函数引发 panic。但不论怎样,如果不加以处理,panic 就会导致程序崩溃并终止运行。原创 2024-01-05 11:36:38 · 364 阅读 · 1 评论 -
20 | 错误处理 (下)
今天,从两个视角总结了错误类型、错误值的处理技巧和设计方式。我们先一起看了一下 Go 语言中处理错误的最基本方式,这涉及了函数结果列表设计、errors.New函数、卫述语句以及使用打印函数输出错误值。接下来,提出的第一个问题是关于错误判断的。对于一个错误值来说,我们可以获取到它的类型、值以及它携带的错误信息。如果我们可以确定其类型范围或者值的范围,那么就可以使用一些明确的手段获知具体的错误种类。否则,我们就只能通过匹配其携带的错误信息来大致区分它们的种类。原创 2024-01-05 10:11:41 · 749 阅读 · 0 评论 -
19 | 错误处理(上)
今天我们一起初步学习了错误处理的内容。我们总结了错误类型、错误值的处理技巧和设计方式,并一起分享了 Go 语言中处理错误的最基本方式。由于错误处理的内容分为上下两篇,在下一次的文章中,我们会站在建造者的角度,一起来探索一下:怎样根据实际情况给予恰当的错误值。原创 2024-01-04 18:14:00 · 898 阅读 · 0 评论 -
18 | if语句、for语句和switch语句
我们今天主要讨论了for语句和switch语句,不过我并没有说明那些语法规则,因为它们太简单了。我们需要多加注意的往往是那些隐藏在 Go 语言规范和最佳实践里的细节。这些细节其实就是我们很多技术初学者所谓的“坑”。比如,我在讲for语句的时候交代了携带range子句时只有一个迭代变量意味着什么。你必须知道在迭代数组或切片时只有一个迭代变量的话是无法迭代出其中的元素值的,否则你的程序可能就不会像你预期的那样运行了。还有,range表达式的结果值是会被复制的,实际迭代时并不会使用原值。原创 2024-01-04 17:37:18 · 874 阅读 · 0 评论 -
17 | go语句及其执行规则(下)
在本篇文章中,我们接着上一篇文章的主问题,讨论了当我们想让运行结果更加可控的时候,应该怎样去做。主 goroutine 的运行若过早结束,那么我们的并发程序的功能就很可能无法全部完成。所以我们往往需要通过一些手段去进行干涉,比如调用time.Sleep函数或者使用通道。我们在后面的文章中还会讨论更高级的手段。另外,go函数的实际执行顺序往往与其所属的go语句的执行顺序(或者说 goroutine 的启用顺序)不同,而且默认情况下的执行顺序是不可预知的。那怎样才能让这两个顺序一致呢?原创 2024-01-04 14:20:15 · 846 阅读 · 0 评论 -
16 | go语句及其执行规则(上)
今天,描述了 goroutine 在操作系统的并发编程体系,以及在 Go 语言并发编程模型中的地位和作用。这些知识点会打下一个坚实的基础。还提到了 Go 语言内部的运行时系统和调度器,以及它们围绕着 goroutine 做的那些统筹调配和维护工作。这些内容中的每句话应该都会对你正确理解 goroutine 起到实质性的作用。你可以用这些知识去解释主问题中的那个程序在运行后为什么会产出那样的结果。原创 2024-01-04 11:53:14 · 391 阅读 · 0 评论 -
15 | 关于指针的有限操作
我们今天集中说了说与指针有关的问题。基于基本类型的指针值应该是我们最常用到的,也是我们最需要关注的,比如*Dog类型的值。怎样得到一个这样的指针值呢?这需要用到取址操作和操作符&。不过这里还有个前提,那就是取址操作的操作对象必须是可寻址的。关于这方面你需要记住三个关键词:不可变的、临时结果和不安全的。只要一个值符合了这三个关键词中的任何一个,它就是不可寻址的。但有一个例外,对切片字面量的索引结果值是可寻址的。那么不可寻址的值在使用上有哪些限制呢?原创 2024-01-04 09:44:20 · 767 阅读 · 0 评论 -
14 | 接口类型的合理运用
Go 语言的接口常用于代表某种能力或某类特征。首先,我们要弄清楚的是,接口变量的动态值、动态类型和静态类型都代表了什么。这些都是正确使用接口变量的基础。当我们给接口变量赋值时,接口变量会持有被赋予值的副本,而不是它本身。更重要的是,接口变量的值并不等同于这个可被称为动态值的副本。它会包含两个指针,一个指针指向动态值,一个指针指向类型信息。基于此,即使我们把一个值为nil的某个实现类型的变量赋给了接口变量,后者的值也不可能是真正的nil。虽然这时它的动态值会为nil,但它的动态类型确是存在的。原创 2024-01-03 20:10:43 · 885 阅读 · 0 评论 -
13 | 结构体及其方法的使用法门
结构体类型的嵌入字段比较容易让 Go 语言新手们迷惑,所以在本篇文章着重解释了它的编写方法、基本的特性和规则以及更深层次的含义。在理解了结构体类型及其方法的组成方式和构造套路之后,这些知识应该是重点掌握的。嵌入字段是其声明中只有类型而没有名称的字段,它可以以一种很自然的方式为被嵌入的类型带来新的属性和能力。在一般情况下,我们用简单的选择表达式就可以直接引用到它们的字段和方法。不过,我们需要小心可能产生“屏蔽”现象的地方,尤其是当存在多个嵌入字段或者多层嵌入的时候。原创 2024-01-03 17:44:56 · 945 阅读 · 0 评论 -
12 | 使用函数的正确姿势
我们今天主要聚焦于函数的使用手法。在 Go 语言中,函数可是一等的(first-class)公民。它既可以被独立声明,也可以被作为普通的值来传递或赋予变量。除此之外,我们还可以在其他函数的内部声明匿名函数并把它直接赋给变量。需要记住 Go 语言是怎样鉴别一个函数的,函数的签名在这里起到了至关重要的作用。函数是 Go 语言支持函数式编程的主要体现。我们可以通过“把函数传给函数”以及“让函数返回函数”来编写高阶函数,也可以用高阶函数来实现闭包,并以此做到部分程序逻辑的动态生成。原创 2024-01-03 14:41:14 · 745 阅读 · 0 评论 -
11 | 通道的高级玩法
今天,我们先讲了单向通道的表示方法,操作符“<-”仍然是关键。如果只用一个词来概括单向通道存在的意义的话,那就是“约束”,也就是对代码的约束。我们可以使用带range子句的for语句从通道中获取数据,也可以通过select语句操纵通道。select语句是专门为通道而设计的,它可以包含若干个候选分支,每个分支中的case表达式都会包含针对某个通道的发送或接收操作。当select语句被执行时,它会根据一套分支选择规则选中某一个分支并执行其中的代码。原创 2024-01-03 11:17:22 · 889 阅读 · 0 评论
分享