Hero Image
全局变量加锁的优化

#golang #cache_line 开发中不可避免地会遇到需要对全局变量加锁的情况,而一旦并发量高了之后,加锁的变量有可能变成服务的性能瓶颈所在。所以千方百计地寻找优化方法。 改变锁的类型 如果业务中的全局变量是读多写少的应用场景,我们可以将互斥锁改为读写锁。即读取时对变量加读锁,这样可以支持多个线程并发读取同一个变量。而只有变量需要修改时才加写锁,保证写的时候不会被其他线程读取到错误的值。 互斥锁: func DoWork() { lock.Lock() defer lock.Unlock() // do something... } 读写锁: func Read() { lock.RLock() defer lock.RUnlock() // read global variable } func Write() { lock.Lock() defer lock.Unlock() // write global variable } 降低锁住的代码块长度 在Go中,我们常常会利用defer关键字的特性,写出如下的代码: func Write() { lock.Lock() defer lock.Unlock() // do something... } 但在实际代码中,如果对全局变量读写前后会有较长时间去做其他工作的情况下,就会造成极大的性能损耗。加锁之后没有立即对全局变量进行读写,或者对全局变量读写完之后没有立即释放锁,都会使其他线程没有办法立即抢到锁,从而拉低了整个系统的并发性能。 根据这个逻辑,可以将上述代码改成如下格式: func Write() { // do something... lock.Lock() // read or write global variable lock.Unlock() // do something.

Hero Image
Golang性能分析工具-pprof

#golang #pprof #内存分析 pprof is a tool for visualization and analysis of profiling data. pprof reads a collection of profiling samples in profile.proto format and generates reports to visualize and help analyze the data. It can generate both text and graphical reports (through the use of the dot visualization package). PProf是用于可视化和分析性能分析数据的工具,PProf以profile.proto读取分析样本的集合,并生成报告以可视化并帮助分析数据(支持文本和图形报告)。 简介 采集方式 runtime/pprof:采集程序(非Server)的指定区块的运行数据进行分析。 net/http/pprof:基于HTTPServer运行,并且可以采集运行时数据进行分析。 gotest:通过运行测试用例,并指定所需标识来进行采集。 功能 CPUProfiling:CPU分析,按照一定的频率采集所监听的应用程序CPU(含寄存器)的使用情况,可确定应用程序在主动消耗CPU周期时花费时间的位置。 MemoryProfiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏。 BlockProfiling:阻塞分析,记录Goroutine阻塞等待同步(包括定时器通道)的位置,默认不开启,需要调用runtime.SetBlockProfileRate进行设置。 MutexProfiling:互斥锁分析,报告互斥锁的竞争情况,默认不开启,需要调用runtime.SetMutexProfileFraction进行设置。 GoroutineProfiling:Goroutine分析,可以对当前应用程序正在运行的Goroutine进行堆栈跟踪和分析。这项功能在实际排查中会经常用到, 因为很多问题出现时的表象就是Goroutine暴增,而这时候我们要做的事情之一就是查看应用程序中的Goroutine正在做什么事情,因为什么阻塞了, 然后再进行下一步。 简单的例子 注意要在import中引入 _ "net/http/pprof" package main import ( "log" "net/http" _ "net/http/pprof" "time" ) func main() { go func() { for { log.

Hero Image
Golang反射

#golang #reflect 反射简介 Golang提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。 reflect 包中的官方注释:Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型, 两个函数分别是: reflect.TypeOf 能获取类型信息 reflect.ValueOf 能获取数据的运行时表示 三大法则 运行时反射是程序在运行期间检查其自身结构的一种方式。反射带来的灵活性是一把双刃剑,反射作为一种元编程方式可以减少重复代码, 但是过量的使用反射会使我们的程序逻辑变得难以理解并且运行缓慢。我们在这一节中会介绍Go语言反射的三大法则,其中包括: 从interface{}变量可以反射出反射对象; 从反射对象可以获取interface{}变量; 要修改反射对象,其值必须可设置; 第一法则 反射的第一法则是我们能将Go语言的interface{}变量转换成反射对象。很多读者可能会对这以法则产生困惑—为什么是从interface{}变量到反射对象? 当我们执行reflect.ValueOf(1)时,虽然看起来是获取了基本类型int对应的反射类型,但是由于 reflect.TypeOf 、 reflect.ValueOf 两个方法的入参都是interface{}类型,所以在方法执行的过程中发生了类型转换。 因为Go语言的函数调用都是值传递的,所以变量会在函数调用时进行类型转换。基本类型int会转换成interface{}类型, 这也就是为什么第一条法则是从接口到反射对象。 上面提到的reflect.TypeOf 和reflect.ValueOf 函数就能完成这里的转换,如果我们认为Go语言的类型和反射类型处于两个不同的世界,那么这两个函数就是连接这两个世界的桥梁。 我们可以通过以下例子简单介绍它们的作用, reflect.TypeOf 获取了变量author的类型, reflect.ValueOf 获取了变量的值ormissia。如果我们知道了一个变量的类型和值,那么就意味着我们知道了这个变量的全部信息。 package main import ( "fmt" "reflect" ) func main() { author := "ormissia" fmt.Println("TypeOf author:", reflect.TypeOf(author)) fmt.Println("ValueOf author:", reflect.ValueOf(author)) } $ go run main.

Hero Image
Go 惯用模式:函数选项模式

#golang 作为 Golang 开发者,遇到的许多问题之一就是尝试将函数的参数设置成可选项。这是一个十分常见的场景,您可以使用一些已经设置默认配置和开箱即用的对象,同时您也可以使用一些更为详细的配置。 问题出发点 对于许多编程语言来说,这很容易。在 C 语言家族中,您可以提供具有同一个函数但是不同参数的多个版本;在 PHP 之类的语言中,您可以为参数提供默认值,并在调用该方法时将其忽略。但是在 Golang 中,上述的做法都不可以使用。那么您如何创建具有一些其他配置的函数,用户可以根据他的需求(但是仅在需要时)指定一些额外的配置。 有很多的方法可以做到这一点,但是大多数方法都不是尽如人意,要么需要在服务端的代码中进行大量额外的检查和验证,要么通过传入他们不关心的其他参数来为客户端进行额外的工作。 下面我将会介绍一些不同的选项,然后为其说明为什么每个选项都不理想,接着我们会逐步构建自己的方式来作为最终的干净解决方案:函数选项模式。 让我们来看一个例子。比方说,这里有一个叫做StuffClient的服务,它能够胜任一些工作,同时还具有两个配置选项(超时和重试)。 type StuffClient interface { DoStuff() error } type stuffClient struct { conn Connection timeout int retries int } 这是个私有的结构体,因此我们应该为它提供某种构造函数: func NewStuffClient(conn Connection, timeout, retries int) StuffClient { return &stuffClient{ conn: conn, timeout: timeout, retries: retries, } } 嗯,但是现在我们每次调用NewStuffClient函数时都要提供timeout和retries。因为在大多数情况下,我们只想使用默认值,我们无法使用不同参数数量带定义多个版本的NewStuffClient,否则我们会得到一个类似NewStuffClient redeclared in this block编译错误。 一个可选方案是创建另一个具有不同名称的构造函数,例如: func NewStuffClient(conn Connection) StuffClient { return &stuffClient{ conn: conn, timeout: DEFAULT_TIMEOUT, retries: DEFAULT_RETRIES, } } func NewStuffClientWithOptions(conn Connection, timeout, retries int) StuffClient { return &stuffClient{ conn: conn, timeout: timeout, retries: retries, } } 但是这么做的话有点蹩脚。我们可以做得更好,如果我们传入了一个配置对象呢: