Go高阶 语言类库

unsafe #

利用unsafe包修改私有成员 #

利用unsafe获取slice和map的长度 #

实现字符串和byte切片的零复制转换 #

context #

译作“上下文”,准确说它是goroutine的上下文。主要用来在goroutine之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v等

使用context几乎成为并发控制和超时控制的标准做法,与它协作的API都可以由外部控制执行“取消”操作,例如:取消一个HTTP请求的执行。

另外,context.Context可以协调多个goroutine中的代码执行“取消”操作,并且可以存储键值对,最重要的是它是并发安全的操作。

在Go的server里,对每个Request(请求)都会启动若干个goroutine同时工作:有些去内存查一些数据,有些去数据库拿数据,有些调用第三方接口获取相关数据等。

这些goroutine需要共享请求的基本信息:例如登陆token,处理请求的最大超时时间(如果超过此值再返回数据,请求方会因为超时接收不到)等。当请求被取消或是处理时间太长,这有可能是使用者关闭了浏览器或是已经超过了请求方规定的超时时间,请求方直接放弃了这次请求结果。这时,所有正在为这个请求工作的goroutine需要快速退出,因为它们的“工作成果”不再被需要了。

**Go语言中的server实际上是一个“协程模型”,处理一个请求需要多个协程。**例如在业务的高峰期,某个下游服务器的响应速度变慢,而当前系统的请求又没有超时控制,或者超过时间设置过大,那么等待下游服务器返回数据的协程就会越来越多。而协程师要消耗资源的,后果就是协程数激增,内存占用飙涨,Go调度器和GC不堪重用,甚至导致服务不可用。更严重的会导致雪崩效应,整个服务器对外表现为不可用,这肯定是P0级别的事故。

其实前面描述的P0级别的事故,通过设置“允许下游最长处理时间”就可以避免。例如,给下游设置timeout是50ms,如果超过这个值还没有接收到返回数据,就直接向客户端返回一个默认值或者错误。例如返回商品的一个默认库数量。注意,这里设置的超时时间和创建一个HTTP client设置的读写超时时间不一样,后者表示一次TCP传输的时间,而一次请求可能包含多次TCP传输,前者则表示所有传输的总时间。

而context包就是为了解决上面所说的问题开发的:在一组goroutine之间传递共享的值、取消信号、deadline等。

在Go里,不能直接杀死协程,协程的关闭一般采用channel和select的方式来控制。但是在某些场景下,例如处理一个请求衍生了很多协程,这些协程之间是相互关联的:需要共享一些全局变量、有共同的deadline等,而且可以同时被关闭。用channel和select就会比较麻烦,这时可以通过context来实现。

context用来解决goroutine之间退出通知、元数据传递的功能问题。

context会在函数中间传递,只需要在适当的时间调用Cancel函数向goroutine发出取消信号或者调用Value函数取出context中的值。

对使用context的几点建议:

  1. 不要将context塞到结构体里。直接将context类型作为函数的第一参数,而且一般都命名为ctx。
  2. 不要向函数传入一个含有nil属性的context,如果实在不知道传什么,标准库准备好了一个context:todo。
  3. 不要把本应该作为函数参数的类型塞到context中,context存储的应该是一些共同的数据。例如,登陆的session、cookie等。
  4. 同一个context可能会传递到多个groutine,但别担心,context是并发安全的。

如何使用context #

传递共享的数据 #

定时取消 #

防止goroutine泄漏 #

context底层原理 #

error #

计时器 #

反射 #

反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。

用比喻来说,反射就是程序在运行的时候能够观察并纠正自己的行为。

Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

使用场景 #

  • 不能明确接口调用那个函数,需要根据传入的参数在运行时决定。
  • 不能明确传入参数的参数类型,需要在运行时处理任意对象。

不推荐使用原因 #

  • 与反射相关的代码,难以阅读。
  • 编译器无法提前发现一些类型错误,可能会运行很久后才会出错,会造成严重后果。
  • 反射影响程序性能,比正常代码运行速度慢一到两个数量级。