Blog

mindmap:Context

Context #

来历 #

Go在1.7的版本中才正式把Cntext加入到标准库中 #

在Go1.9中,还专门实现了一个叫做type alias的新特性 #

适用场景 #

上下文信息传递 #

控制子goroutine的运行 #

超时控制的方法调用 #

可以取消的方法调用 #

基本使用方法 #

四个实现方法 #

  • Deadline方法

    会返回这个Context被取消的截止日期

  • Done方法

    返回一个Channel对象

  • Err

    如果Done没有被close,Err方法返回nil; 如果Done被close,Err方法返回Done被close的原因

  • Value

    返回此ctx中和指定的key相关联的value

常用的生成顶层Context的方法 #

  • context.Background()

    返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。

  • context.TODO()

    返回一个非nil的、空的Context,没有任何值,不会被cancel,不会超时,没有截止日期。

使用规则 #

  • 一般函数使用Context时,会把这个参数放丰第一个参数的位置
  • 从来不把nil当作Context类型的参数值,可以使用context.Background()创建一个空的上下文对象
  • 只用作临时做函数之间的上下文透传,不能持久化Context或者Context长久保存
  • key的类型不应该使用字符串类型或者其它内奸类型,否则容易在包之间使用Context时候冲突
  • 常常使用struct{}作为底层类型定义key的类型

创建特殊用途Context的方法 #

WithValue #

基于parent Context生成一个新的Context,保存了一个key-value键值对,常常用来传递上下文。

...

WithDeadline返回的cancel的正确用法

Content #

和 cancelCtx 一样,WithDeadline(WithTimeout)返回的 cancel 一定要调用,并且要尽可能早地被调用,这样才能尽早释放资源,不要单纯地依赖截止时间被动取消。正确的使用姿势是啥呢?我们来看一个例子。

func slowOperationWithTimeout(ctx context.Context) (Result, error) {
  ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
  defer cancel() // 一旦慢操作完成就立马调用cancel
  return slowOperation(ctx)
}

Viewpoints #

From #

11 | Context:信息穿透上下文

WithCancel

Content #

WithCancel 方法返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。

我们常常在一些需要主动取消长时间的任务时,创建这种类型的 Context,然后把这个 Context 传给长时间执行任务的 goroutine。当需要中止任务时,我们就可以 cancel 这个 Context,这样长时间执行任务的 goroutine,就可以通过检查这个 Context,知道 Context 已经被取消了。

WithCancel 返回值中的第二个值是一个 cancel 函数。其实,这个返回值的名称(cancel)和类型(Cancel)也非常迷惑人。

记住,不是只有你想中途放弃,才去调用 cancel,只要你的任务正常完成了,就需要调用 cancel,这样,这个 Context 才能释放它的资源(通知它的 children 处理 cancel,从它的 parent 中把自己移除,甚至释放相关的 goroutine)。很多同学在使用这个方法的时候,都会忘记调用 cancel,切记切记,而且一定尽早释放。

我们来看下 WithCancel 方法的实现代码:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)// 把c朝上传播
    return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}

代码中调用的 propagateCancel 方法会顺着 parent 路径往上找,直到找到一个 cancelCtx,或者为 nil。如果不为空,就把自己加入到这个 cancelCtx 的 child,以便这个 cancelCtx 被取消的时候通知自己。如果为空,会新起一个 goroutine,由它来监听 parent 的 Done 是否已关闭。

...

Context链式查找

Content #

Go 标准库实现的 Context 还实现了链式查找。如果不存在,还会向 parent Context 去查找,如果 parent 还是 valueCtx 的话,还是遵循相同的原则: valueCtx 会嵌入 parent,所以还是会查找 parent 的 Value 方法的。

ctx = context.TODO()
ctx = context.WithValue(ctx, "key1", "0001")
ctx = context.WithValue(ctx, "key2", "0001")
ctx = context.WithValue(ctx, "key3", "0001")
ctx = context.WithValue(ctx, "key4", "0004")

fmt.Println(ctx.Value("key1"))

Viewpoints #

From #

11 | Context:信息穿透上下文

newaxis在NumPy索引中的含义

Content #

newaxis对象会将选择的结果扩展一维(one unit-length dimension),新加的维度即在newaxis对象所在的位置。

>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]])
>>> x.shape
(2, 3, 1)
>>> x[:, np.newaxis, :, :].shape
(2, 1, 3, 1)

newaxis在此外只是None的别名。

newaxis很适合在合并两个数组时使用。

>>> x = np.arange(5)
>>> x[:, np.newaxis] + x[np.newaxis, :]
array([[0, 1, 2, 3, 4],
      [1, 2, 3, 4, 5],
      [2, 3, 4, 5, 6],
      [3, 4, 5, 6, 7],
      [4, 5, 6, 7, 8]])

From #

Ellipsis在NumPy索引中的含义

Content #

Ellipsis会扩展成多个 :,其个数会保证原数组所有的维度上都会被选择。

>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]])
>>> x.shape
(2, 3, 1)
>>> x[..., 0]
array([[1, 2, 3],
      [4, 5, 6]])

x有三个维度,因此Ellipsis会被扩展成两个 :,以保证每个维度都被选择。

From #

bochs调试汇编代码

Content #

计算机启动,总是把引导程序加载到物理内存地址0x7c00处,可以把这个地址设置为断点: b 0x7c00

r - 显示通过寄存器命令 sreg - segment register,显示段寄存器的内容 creg - control register,显示控制寄存器的内容 xp - eXamine memory at Physical address,显示物理内存地址处的内容每次只显示一个双字,显示多个双字,需要用"“附加一个数量。 u - 反汇编指令。第一个参数跟在”“后面,其值表示反汇编出多少条指令。第二个参数用于指定内存地址。

rep movsb, loop等指令会反复执行,此时如果使用s命令,就会反复陷入同一语句,使用调试命令n,可让bochs自动完成循环过程,并在循环体下一条指令前停住。n命令是通过寄存器CX的值来监视循环的过程。

条件跳转指令形成的循环无法用n来跳过,这时可以先用u命令找出循环体外指令的地址,再在该地址设置断点,然后用c命令跳过循环。

info eflags - 查看标志位 info gdt - 显示GDTR的内容

href与getAttribute('href')的区别

Content #

有HTML代码如下: <a href="//m.taobao.com" ></div>

有JavaScript代码如下: var a = document.getElementByTagName(‘a’);

请问以下两个表达式的结果会有何区别? a.href a.getAttribute(‘href’)

a.href //http://m.taobao.com”,这个 URL 是 resolve 过的结果 a.getAttribute(‘href’) // “//m.taobao.com”,跟 HTML 代码中完全一致

From #

sync.Pool的数据结构

Content #

sync.Pool 的数据结构如下图所示:

Pool 最重要的两个字段是 local 和 victim,因为它们两个主要用来存储空闲的元素。弄清楚这两个字段的处理逻辑,你就能完全掌握 sync.Pool 的实现了。下面我们来看看这两个字段的关系。

每次垃圾回收的时候,Pool 会把 victim 中的对象移除,然后把 local 的数据给 victim,这样的话,local 就会被清空,而 victim 就像一个垃圾分拣站,里面的东西可能会被当做垃圾丢弃了,但是里面有用的东西也可能被捡回来重新使用。

victim 中的元素如果被 Get 取走,那么这个元素就很幸运,因为它又“活”过来了。但是,如果这个时候 Get 的并发不是很大,元素没有被 Get 取走,那么就会被移除掉,因为没有别人引用它的话,就会被垃圾回收掉。

Viewpoints #

From #

10 | Pool:性能提升大杀器

sync.Pool使用注意点

Content #

sync.Pool 数据类型用来保存一组可独立访问的临时对象。请注意这里加粗的“临时”这两个字,它说明了 sync.Pool 这个数据类型的特点,也就是说,它池化的对象会在未来的某个时候被毫无预兆地移除掉。而且,如果没有别的对象引用这个被移除的对象的话,这个被移除的对象就会被垃圾回收掉。

sync.Pool 本身就是线程安全的,多个 goroutine 可以并发地调用它的方法存取对象;

sync.Pool 不可在使用之后再复制使用。

From #