golang中context包的使用

context包定义了Context类型,这个类型在API边界即进程中传递截止日期、同步信号,请求值等相关信息。 1. 对context包的介绍 在服务器的传入请求中应包含context,而对服务器的传出调用应接收一个context。它们之间的调用链必须包含context,或是衍生的WithCancel, WithDeadline, WithTimeout, WithValue。当一个WithCancel Context被“cancel”,那么当前context所派生的所有context也都将被取消。 WithCancel, WithDeadline, WithTimeout接收一个Context对象(父对象),并返回其父对象的一个携带有cancel/deadline/timeout的一个拷贝(子对象)。调用CancelFunc会取消其子对象及子对象的子对象等,删除父对象对子对象的引用,并停止所有关联的计时器。未能调用CancelFunc将造成父对象结束前或计时器被触发前子对象的泄露。使用go vet工具可以检查所有控制流路径上是否都使用了CancelFunc 使用context的程序应遵循以下规则,以使各个包之间的接口保持一致,并启用静态分析工具来检查上下文传播: 不要将context存储在结构类型中,而是将context明确传递给需要它的每个函数。Context应该是第一个函数,通常命名为ctx。 func DoSomething(ctx context.Context, arg Arg) error { // ...use ctx... } 不要传递一个值为nil的context,即使一个函数允许这样做。如果你不确定Context的作用那就请传递context.TODO。 只在进程和API间传递请求范围数据时使用context值,不要用于将可选参数传递给函数。 同样的Context可以传递给运行在不同goroutine中的函数,Context是线程安全的。 2. Context接口 type Context interface { Done() <-chan struct{} Err() error Deadline() (deadline time.Time, ok bool) Value(key interface{}) interface{} } Context是一个接口,其定义非常的简单,只包含4个方法: Done() <-chan struct{} Done()方法将一个channel作为取消信号返回给持有context的函数,当该channel被关闭(即Done()被调用),这些函数应该立即停止其工作并返回。 Err() error Err()返回一个Error,说明为什么取消context。如果Done()没有被调用,那么Err返回nil。 Deadline() (deadline time.Time, ok bool) Deadline()方法返回持有这个context的函数的预期结束时间。如果并没有设置deadline,那么返回的ok将被设置为false。...

May 23, 2021 · 3 min · 李昌

Golang中反射reflect的基本使用

在计算机领域,反射是指一类应用,它们能够自描述和自控制。也即是说,这类应用通过采用某种机制来实现对自己行为的描述和监测,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。 反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥(mi)补了静态语言在动态行为上的不足。 反射(reflect)是在计算机程序运行时,访问,检查,修改它自身的一种能力,是元编程的一种形式。 Go语音提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。 1. 为何我们需要反射? fmt.Fprintf函数提供字符串格式化处理逻辑,它可以对任意类型的值格式化并打印,甚至支持用户自定义的类型。 让我们也来尝试实现一个类似功能的函数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint类似的格式化后的字符串。我们实现的函数名也叫Sprint。 这里我们使用switch类型分支来对不同的类型进行处理。 func Sprint(x interface{}) string { type stringer interface { String() string } switch x := x.(type) { case stringer: return x.String() case string: return x case int: return strconv.Itoa(x) // ...similar cases for int16, uint32, and so on... case bool: if x { return "true" } return "false" default: // array, chan, func, map, pointer, slice, struct return "?...

May 22, 2021 · 4 min · 李昌

野生Goroutine带来的问题及对应解决方案

一、野生goroutine的问题 引言: 毋庸置疑,golang原生的goroutine极大降低了程序员使用并发编程的代价,程序员们不需要再去关心如何实现接口、如何继承Thread类等多余的操作,只需要简简单单的go, 就可以开启一个并发的协程。但这种简单的使用方式同时也带来一些问题,这些goroutine不再受我们控制了,它们在运行时可能会发生任何错误或意外,而我们无法得知或去处理这些意外。我们将启动后不再受主进程控制的goroutine称为野生goroutine,本节将介绍野生goroutine存在的一些问题并介绍一些简单的解决方法。 1. goroutine中panic无法恢复 正常的函数中panic的recover import ( "fmt" ) func main(){ defer func(){ if err := recover(); err != nil{ fmt.Println(err) } }() var bar = []int{1} fmt.Println(bar[1]) } reflect: slice index out of range goroutine中panic的恢复 func main(){ defer func(){ if err := recover(); err != nil { // 在这里使用recover(),不能捕获panic fmt.Println("catch you, bad guy") } }() go func(){ fmt.Println("I'm in a goroutine") panic("come to catch me") }() time....

May 19, 2021 · 17 min · 李昌

Go中的锁

1. sync.Mutex互斥锁 不同goroutine之间对公共资源进行访问需要使用互斥锁。例如在对银行账户的操作中,如果我们有两种操作,一个是查询余额,一个是存款。其操作如下: package bank // 存款余额 var balance int // 存款 func Deposit(amount int) { balance = balance + amount } // 查询 func Balance() int { return balance } // Alice: go func() { bank.Deposit(200) // A1 fmt.Println("=", bank.Balance()) // A2 }() // Bob: go bank.Deposit(100) // B 这其中,若把A1分为两个操作,A1r:把余额从内存中读出来;A2w:把修改后的余额写入内存。 若执行顺序为A1r → B → A1w → A2, 正常情况下,Alice和Bob分别存入了$200,$100,因此最后的存款应该是300,但最后输出结果为200。因为A在计算时是按照A1r读出的数值进行计算,忽略了B的操作,A与B之间发生了数据竞争。 数据竞争:无论任何时候,只要有两个goroutine并发访问同一变量,且至少其中的一个是写操作的时候就会发生数据竞争。 解决此问题的办法之一是使用互斥锁。 import "sync" var ( mu sync.Mutex // guards balance balance int ) func Deposit(amount int) { mu....

April 14, 2021 · 1 min · 李昌

面试题golang

三个goroutine分别输出张三、李四、王五,使其按上述顺序输出5遍。 package main import ( "fmt" "sync" ) var w sync.WaitGroup func main() { w.Add(15) chan1 := make(chan struct{}, 0) chan2 := make(chan struct{}, 0) for i := 0; i < 5; i++ { go func() { defer w.Done() fmt.Println("张三") chan1 <- struct{}{} }() go func() { defer w.Done() <- chan1 fmt.Println("李四") chan2 <- struct{}{} }() go func() { defer w.Done() <- chan2 fmt.Println("王五") }() } w.Wait() } 编写程序输出某目录下的所有文件(包括子目录) package main import ( "fmt" "io/ioutil" "os" ) func main() { dir := os....

April 14, 2021 · 1 min · 李昌

slice和数组的区别

1. 长度 数组 对于数组来说,它的长度是固定的,并且数组的长度是其类型的一部分,即对于以下两个数组来说,他们是不同的类型。 var a [5]int var b [6]int fmt.Printf("%v", reflect.TypeOf(a) == reflect.TypeOf(b)) // 输出: false 数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。 对于数组来说,由于其长度是固定的,因此不能添加或删除元素。 切片 而对于切片,其长度是不固定的,不同长度的切片,只要其元素类型相同,则它们就是相同的切片类型。 a := make([]int, 5) b := make([]int, 6) fmt.Printf("%v\n", reflect.TypeOf(a) == reflect.TypeOf(b)) // 输出: true 如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了 slice,因为新slice的长度会变大: months := [...]string{1: "January", /* ... */, 12: "December"} summer := months[6:9] fmt.Println(summer[:20]) // panic: out of range endlessSummer := summer[:5] // extend a slice (within capacity) fmt.Println(endlessSummer) // "[June July August September October]" 2....

March 30, 2021 · 2 min · 李昌

Reader和Writer接口

1. Reader接口 type Reader interface { Read(p []byte) (n int, err error) } 接口说明 Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中占用 len(p) 个字节作为暂存空间。若可读取的数据不到 len(p) 个字节,Read 会返回可用数据,而不是等待更多数据。 当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF (end-of-file),它会返回读取的字节数。它可能会同时在本次的调用中返回一个non-nil错误,或在下一次的调用中返回这个错误(且 n 为 0)。 一般情况下, Reader会返回一个非0字节数n, 若 n = len(p) 个字节从输入源的结尾处由 Read 返回,Read可能返回 err == EOF 或者 err == nil。并且之后的 Read() 都应该返回 (n:0, err:EOF)。...

March 28, 2021 · 1 min · 李昌

goroutine和线程

1. 线程 在操作系统中,进程是分配资源的基本单位,但当进程作为调度的基本单位时,会造成较大的开销,频繁的进程调度将消耗大量时间。因此引出了线程:线程是处理器调度的基本单位,线程只拥有很小的运行时必要的资源。一个进程可拥有多个线程,同一个进程中的所有线程共享进程获得的主存空间和资源。 线程的实现 有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程 和内核级线程的连接方式:多对一模型、一对一模型、多对多模型。 2. goroutine 在Go语言中,每一个并发的执行单元叫作一个goroutine,是一种轻量级的线程。 3. 线程与goroutine的区别 运行时栈的大小 每个系统级线程都会有一个固定大小的栈(一般为2MB),主要用于保存函数递归调用时参数和局部变量。这造成了两个问题: 对于某些需要很小的栈空间的线程来说是一个巨大的浪费 对于少数需要巨大栈空间的线程来说又面临栈溢出的风险 goroutine会以一个很小的栈启动(2KB或4KB),当遇到深度递归时导致当前栈空间不足,会根据需要动态的伸缩栈的大小。 调度 go的运行时还包括了其自己的调度器,可以在n个操作系统线程上多工调度m个goroutine(类似于多线程模型中的多对多模型)。 go调度器的工作和内核的调度时相似的,但是这个调度器只关注单独的go程序中的goroutine。 goroutinie采用的是半抢占式的协作调度,只有当当前goroutine发生阻塞时才会导致调度。 这种调度发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价比系统线程要低得多。 创建和销毁 Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。- goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。

March 27, 2021 · 1 min · 李昌

Flag包的基本用法

Flag包的基本用法 flag包用于处理golang命令行程序中的参数 1. 使用flag包的基本流程 使用flag包涉及三个步骤: 定义变量以捕获标志值 定义Go应用程序将使用的标志 在执行时解析提供给应用程序的标志。 flag软件包中的大多数功能都与定义标志并将其绑定到定义的变量有关。解析阶段由Parse()函数处理。 一个例子 创建一个程序,该程序定义一个布尔标志,该标志会更改将打印到标准输出的消息。如果-color提供了一个标志,程序将以蓝色打印一条消息。如果未提供标志,则消息将被打印为没有任何颜色。 // boolean.go import ( "flag" "fmt" ) type Color string // 定义变量以捕获标志值 const ( ColorBlack Color = "\u001b[30m" ColorRed = "\u001b[31m" ColorGreen = "\u001b[32m" ColorYellow = "\u001b[33m" ColorBlue = "\u001b[34m" ColorReset = "\u001b[0m" ) func colorize(color Color, message string) { fmt.Println(string(color), message, string(ColorReset)) } func main() { useColor := flag.Bool("color", false, "display colorized output") // 定义Go应用程序将使用的标志 flag....

February 25, 2021 · 1 min · 李昌

go get代理方案

直接干终极方案: go env -w GOPROXY=https://goproxy.cn,direct 修改hosts,然后reboot 添加 192.30.253.112 github.com 151.101.185.194 github.global.ssl.fastly.net 到/etc/hosts 然后reboot go get golang.org 在使用go get golang.org/...时,总是time out(就算fp也一样,fp之后可以访问golang.org),不知道为啥。 幸好github上存在golang.org的镜像 例如 go get -u golang.org/x/net 那么这个包的位置在github上就是github.com/golang/net, 所以,我们可以手动建立golang.org/x/目录,并切换到该目录下,然后使用 git clone https://github.com/golang/net.git **注意:**要使用git clone命令,直接下载下来复制到目录下会提示找不到版本号。 终极方案 go env -w GOPROXY=https://goproxy.cn,direct

February 25, 2021 · 1 min · 李昌