To Bangkok

Visa

普通落地签

http://www.thaiembassy.com/thailand/visa-on-arrival.php

照片

https://ask.qyer.com/question/788823.html

白底,4X6 CM

(1)《申请表》(Application forVisa on Arrival)、1张白/蓝底4*6cm照片、15天内往返机票、有效期超过6个月的护照。(注:表格可下载并事先用英文填好;在泰如无联系人,可填写拟住酒店地址、电话)。
(2)签证申请费每人1000铢。现场拍照另交费。申请落地签时,需备1万铢、每个家庭2万铢或等值外币以证明旅游期间有经济能力。泰移民官将抽查。

  1. 落地签有效期15天(包括入境当天),如为以旅游为目的则不可延期。离境时签证过期需向移民局缴纳罚款,1天500铢,最高不超2万铢。

https://zhuanlan.zhihu.com/p/33490197
https://zhuanlan.zhihu.com/p/38488819

表格事先填好

电子落地签证

不排队,1分钟快速入境!泰国电子落地签证(EVOA)申请攻略

文章介绍去这个页面填写信息就好 http://www.evisathailand.com,525 泰铢。仅需要护照,不需要照片。到达航班填写航班号。

https://bbs.qyer.com/thread-3087627-1.html 中提到 evisathailand 是个代办公司。(我:没想到水这么深。)

但是,这个确实有人说很快,相当于一个官方背景的企业 …… 可以参照这个帖子:https://bbs.qyer.com/thread-3103675-1.html

不要办e-visa落地签,没有任何意义- 泰国- 论坛- 穷游网

文章说和其他落地签一起排队,时间很长。而且电子落地签的二维码只能在特定窗口处理,甚至比直接落地签还要慢。

https://bbs.qyer.com/thread-3083879-4.html 的评论中提到,电子落地签有 booths,比一般的落地签快。

官方网站

https://extranet.immigration.go.th/voaonline/voaonline/VoaonlineAction.do

网上申请落地签
泰国落地签现可以通过网络进行申请,你只需要通过此网站 www.immigration.go.th 填写泰国落地签表格,即可申请泰国落地签证。

素旺纳普机场二楼提供两个柜台为需要进行办理落地签的游客服务,所需资料如下:

落地签需要的条件
落地签证只在有效期内使用

• 持有泰国政府批准国家的护照者。
• 护照必须是真实的,不低于 30 天有效期。
• 泰国旅游的期限不超过 15 天。
• 有到达泰国时,不超过 15 天的回程机票。
• 报实际和能查询的泰国地址。
• 非佛历 2522 年的入境黑名单者
• 签证费仅收泰铢(现金)/不可退款
• 入境时个人必须随身携有外币不低于 10,000 泰铢, 家庭不低于 20,000 泰铢

这个网站,多次在填写完信息后点击下一步进入空白页。

实际

使用 https://thailandevoa.vfsevisa.com 申请的,交了 600B,感觉比 515B 还多 ……

好吧,有点失误,VFS Global 也是一家代理公司。

换汇

https://ask.qyer.com/question/1204168.html
https://ask.qyer.com/question/3487229.html

  1. 机场 ATM 每次要收 150B 手续费
  2. 国内银行预约(https://ask.qyer.com/question/3452511.html)
    • 国内一般只要提前一天预约就可以,不一定要本人换,其他人也可以帮你换,如果分行储备很充足,当天也可以换到,你试试不一定来不及吧
    • 机场也有换,就是汇率不是很划算,如果担心那边没人可以再出发的机场换,你早点到机场,先换一点够用就行,剩下你过了关再换。
    • 华夏银行卡 http://wenzhang.16fan.com/a/98638.html
  3. 换汇处,之后去super rich换汇率最划算

实际发现:

  1. VFS Global 和 evisathailand 是两个不同的电子落地签代办公司,他们在素万那普机场有各自独立的窗口,在普通的落地签证队伍旁边。我深夜到达的时候人很少,只有几个人在电子落地签队伍,入关很快。
  2. VFS Gloabal 是 600+THB,evisathailand 是 525 THB。VFS Global 最后打出来的纸上没有二维码,evisathailand 的有。
  3. 入关只需要那张纸、护照和入境卡三样东西。
  4. Grab 很方便,到市区 300 多泰铢吧。
  5. 中国银行长城 Visa 白金信用卡汇率还不错。
  6. 不要尝试曼谷的中餐馆,贵。
  7. 大皇宫人很多,暹罗博物馆还不错,学生 50 泰铢,其他外国人 200 泰铢。

Go to Rust (一)

这几天看了 Rust 文档,把一些概念整理一下。

  • 通过 cargo 新建一个项目,然后去管理其生命流程,这种现代做法很方便。
  • 对于 statementexpression 的使用方式和 scheme 有些类似,可以返回最后一个 expression 的值。
  • 可能返回错误的地方使用 Result 类型,很类似 Haskell 的处理流程。
  • 模式匹配的方式很像 Ocaml 。
  • ownership 机制很新颖,限制能够带来巨大的力量。让我想到了《全职猎人》中对某项能力增加限制条件可以增加这个能力的威力的设定。
  • Option 类型就是 Haskell 的 Maybe
  • generics 的设计不知道有没有参考 C++ 的 template 概念。

目前只看到文档的第 11 章,Rust 语言的很多概念都能够在其他语言找到对应,只有 ownership 机制是我第一次见到,觉得新颖有趣。

pip 离线安装包

1. 使用场景

在没有网络的设备上使用 pip 安装包。下面以 sklearn 包为例展示如何在没有网络的环境下安装包。

2. 下载包到本地缓存

首先进入一个目录,在这个例子里是 /Users/bef0rewind/Downloads/pip-tmp 目录。

1
pip download sklearn

我这里下载到了一个缓存目录 /Users/bef0rewind/Downloads/pip-tmp,随便选一个就好。pip download 只会下载对应的包,不会进行安装。

此时使用,pip freeze 可以看到已经安装的包,如果之前没有安装过 sklearn,显示的列表里是没有这个包的。

3. 断网安装

为了展示没有网络的情况下如何安装,我断开网络进行了验证。

1
pip install --no-index --find-links=/Users/bef0rewind/Downloads/pip-tmp sklearn

其中 --find-linkspip 从指定的目录里寻找安装包。

4. 其他

如果要用 Python3,而系统默认的版本是 Python 2,则可以将 pip 命令换成 pip3

还在下雨

早上闹钟还没响,就收到学弟从美东发来的微信消息,高管在加拿大被扣留,感觉刚释放的贸易战缓和信号瞬间消失无影。接着看到同学发送的新闻,张首晟教授去世,又给学弟把这则消息转了过去。

到了公司也没获得更多信息。一天工作下来,解决了几个问题,心情是近来最平静的一天。雨一直下个不停,沿着玻璃幕墙下来,汇成一条条水线流下。

各类信息媒介上也是消息攒动,同样没获得更多有效信息。前段时间看到一个问题:为什么感觉今年逝去的名人特别多?我想,也许是他们的时代逝去了,也许是我们的时代逝去了。

小学、初中、高中的时候喜欢数学和诗歌。现在对一些人向往的“诗意的栖居”这种描述不怎么向往了。在这个世界,不可能所有人都能到达那种美好的彼岸。现实还是要战斗,在枪炮中让玫瑰绽放。

有观点说外面的世界再乱,只要自己的小环境能保持美好就够了。我也不知道要怎么选择,一是我不知道什么叫好的小环境,而是我可能也没能力隔绝外界的干扰,也就无所谓选择。

晚上从公司回来,雨还是在下,风吹在手上冻得不行。

寒冷 / 困顿 / 饥饿
树影 / 摇曳 / 零落
混乱 / 猜测 / 迷惑
启程 / 归来 / 此刻

Escape from escape analysis

1. 逃逸分析背景

Go 语言采用了并发的(Concurrent)、非移动的(Non-Movable)、非分代的(Non-Generational)、基于三色(Tri-color)标记的垃圾回收(Garbage Collection)算法,只在 特定阶段开启写屏障(write barrier)。
特点是全局停顿时间比较少,在一些场景下是十微秒级别的。

垃圾回收算法针对的是堆(heap)中的内存。
为了减少垃圾回收的时间消耗,Go 语言在编译阶段通过静态分析算法对程序的结构进行分析,尽可能讲对象分配在栈上(如果这个对象的生命周期在它定义的函数返回时就结束的话)。
这一算法也利用了 Go 语言在函数传递参数时总是传递参数的值这一个语言特性。

而静态分析不总是完备的,会有一些本来可以分配在栈上的对象被 Go 的编译器分配在了堆上。
如这篇文章《Golang escape analysis》所描述的一些例子一样,有些对象本来可以避免逃逸(Escape,指的是对象被分配在堆上)。

对于某些场景,我们确定一个对象肯定可以(也应当)被分配在栈上,但是它却逃逸了。
这样在某些关键路径上的逃逸的对象会造成大量的分配和垃圾回收。

2. Go 版本

使用的 Go 版本为今晚刚从 master 分支上 pull 下的源码直接构建。

1
2
ThinkPad-X1-Carbon:bin bef0rewind$ ./go version
go version devel +42e8b9c3a4 Fri Nov 30 15:17:34 2018 +0000 darwin/amd64

3. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// file: escape.go
package main

import "fmt"

type BigTempObject struct {
/// ...
field1 int
}

func causeEscape(i interface{}) {
switch i.(type) {
case *BigTempObject:
println(i)
default:
fmt.Println(i)
}
}

func main() {
obj := BigTempObject{}
addrObj := &obj

causeEscape(addrObj)
}

使用 go run -gcflags="-m -m" escape.go 可以在运行时输出逃逸分析的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
./escape.go:10: cannot inline causeEscape: unhandled op TYPESW
./escape.go:19: cannot inline main: non-leaf function
./escape.go:10: leaking param: i
./escape.go:10: from ... argument (arg to ...) at ./escape.go:15
./escape.go:10: from *(... argument) (indirection) at ./escape.go:15
./escape.go:10: from ... argument (passed to call[argument content escapes]) at ./escape.go:15
./escape.go:15: causeEscape ... argument does not escape
./escape.go:23: addrObj escapes to heap
./escape.go:23: from addrObj (passed to call[argument escapes]) at ./escape.go:23
./escape.go:21: &obj escapes to heap
./escape.go:21: from addrObj (assigned) at ./escape.go:21
./escape.go:21: from addrObj (interface-converted) at ./escape.go:23
./escape.go:21: from addrObj (passed to call[argument escapes]) at ./escape.go:23
./escape.go:20: moved to heap: obj
(0x10904e0,0xc420080050)

obj 可以分配在栈上,因为在 main 函数返回时(栈退出),这个变量占用的空间就可以安全被用在其他地方了。
但是 “./escape.go:20: moved to heap: obj” 说明 obj 被分配在了堆上。

4. 小技巧

如何改变这个分析结果,需要一点小技巧。

关键词是 uintptr 类型。
Go 语言中对 uintptr 是这样描述的:

uintptr is an integer type that is large enough to hold the bit pattern of any pointer.

比如在 64-bit Linux 系统上 uintptr 被定义成为了 uint64
Go 中合法的类型转换为:normal pointerunsafe.Pointeruintptr
因此我们可以把上面的程序中的 addrObj 转换为 uintptr
这样 Go 编译器不再认为 addrObj 同后面函数 causeEscape 使用的参数 i 存在引用关系,从而绕过 Escape Analysis Algorithm 。
为了防止垃圾回收过程中 obj 被回收,可以使用 obj.field1 = 0 来保持 obj 活跃。

修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"unsafe"
)

type BigTempObject struct {
/// ...
field1 int
}

func causeEscape(i interface{}) {
switch i.(type) {
case *BigTempObject:
println(i)
default:
fmt.Println(i)
}
}

func main() {
obj := BigTempObject{}
addrObj := &obj
intAddr := uintptr(unsafe.Pointer(addrObj))
causeEscape((*BigTempObject)(unsafe.Pointer(intAddr)))
obj.field1 = 0
}

使用 go run -gcflags="-m -m" escape.go 运行结果:

1
2
3
4
5
6
7
8
9
10
11
./escape.go:13: cannot inline causeEscape: unhandled op TYPESW
./escape.go:22: cannot inline main: non-leaf function
./escape.go:13: leaking param: i
./escape.go:13: from ... argument (arg to ...) at ./escape.go:18
./escape.go:13: from *(... argument) (indirection) at ./escape.go:18
./escape.go:13: from ... argument (passed to call[argument content escapes]) at ./escape.go:18
./escape.go:18: causeEscape ... argument does not escape
./escape.go:26: (*BigTempObject)(unsafe.Pointer(intAddr)) escapes to heap
./escape.go:26: from (*BigTempObject)(unsafe.Pointer(intAddr)) (passed to call[argument escapes]) at ./escape.go:26
./escape.go:24: main &obj does not escape
(0x10904e0,0xc42003bf70)

可以看到 obj 不再逃逸,主要是 intAddr 中断了逃逸分析算法构建的指针依赖关系(表示为一个有向图)。

5. 一点感想

我们可以做到不代表一定去做,有风险也不代表禁区,采取什么样的行动是个人权衡后的选择。
什么原因导致了人们做了不同的选择,而人们不同的选择又导致了什么结果?
多样性是这个世界的现状,黑暗面与光明面同在。
May the force be with you.

Golang Receiver Type 探索

1. 参考

在 Go 的官方 spec 中有以下涉及到类型和方法的章节,如果需要了解具体的细节,可以参考阅读。

核心的概念是 method sets:

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type T is the set of all methods declared with receiver T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

下面的一些细节基本上都和这段描述相关。

2. Duck typing 与方法调用

在很多面向对象的语言中,一个对象都可以“拥有”一些方法,使用例如 obj.f(a, b, c) 的形式进行调用。结合语言的类型系统,通过“扩展”、“继承”、“实现”等术语,我们可以将不同的类组织起来。在 Go 语言中采用的是 “duck typing”,没有显式的类型关系定义关键字。当一个类型实现了一个接口的全部方法时,那这个类型就被视为实现了这个接口。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Duck interface {
Bark()
}

type A struct {}

type B struct {}

func (*A) Bark() {}

func main() {
var iA interface{} = &A{}
if _, ok := (iA).(Duck); ok {
fmt.Println("&A{} is Duck")
} else {
fmt.Println("&A{} is not Duck")
}

var iB interface{} = &B{}
if _, ok := (iB).(Duck); ok {
fmt.Println("&B{} is Duck")
} else {
fmt.Println("&B{} is not Duck")
}
}
1
2
&A{} is Duck
&B{} is not Duck

我们可以用原始的类型去调用一个方法,也可以使用一个接口去调用方法。这里就涉及到方法调用者的问题:什么样的对象是一个合法的方法调用者?

至少 A{} 不是,因为我们实现 Duck 接口的时候,使用的是 func (*A) Bark() 进行的定义,而非 func (A) Bark()。这样就导致了只有 A 类型对象的指针类型才能作为方法调用者去调用 Bark 方法。

3. 成员函数的参数

在实现中,调用某个类型的成员方法,第一个参数其实是这个方法的实现对象自身,即如果是一个指针的方法,就是这个指针的值,如果是一个对象,就是这个对象的值。

下面使用 Go 1.8.3 展示,因为当前最新的 Go 编译器在打印 stack trace 的时候不再打印函数的参数(这个例子中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

type R1 struct {
n int
}

func (r *R1) f(n int) {
println("received", n)
panic("just a panic")
}

func main() {
r := &R1{}
a := 1
println(r)
r.f(a)
}
1
2
3
4
5
6
7
8
9
0xc420039f70
received 1
panic: just a panic

goroutine 1 [running]:
main.(*R1).f(0xc420039f70, 0x1)
/Users/bef0rewind/Projects/net example/src/main/receiver_type.go:9 +0xa3
main.main()
/Users/bef0rewind/Projects/net example/src/main/receiver_type.go:16 +0x5a

Stack trace 中函数 f 第一个值是指针 r 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

type R1 struct {
n int
m int
}

func (r *R1) f(n int) {
println("received", n)
panic("just a panic")
}

func (r R1) g(n int) {
println("received", n)
panic("just a panic")
}

func main() {
r := R1{7, 9}
a := 1
println(r.n)
(r).g(a)
}
1
2
3
4
5
6
7
8
9
7
received 1
panic: just a panic

goroutine 1 [running]:
main.R1.g(0x7, 0x9, 0x1)
/Users/bef0rewind/Projects/net example/src/receiver_type/main/args.go:15 +0xa3
main.main()
/Users/bef0rewind/Projects/net example/src/receiver_type/main/args.go:22 +0x58

Stack trace 中函数 g 第一个值是 r 的值 79

从这个实现方式中我们可以推断以下几点:

  • Go 语言采用参数传值的方式进行函数调用,因此如果对象很大,使用的对象本身调用函数会带来大量的复制
  • 不可能在函数调用中改变函数外的调用者,因为传到函数内部的只是调用者的副本

4. 使用接口调用函数

基于这样的成员函数实现方式,我们可以尝试另外一种调用方式:使用接口类型调用一个函数。
这里不是将一个对象转换成特定的接口然后去调用函数,而是使用接口类型本身去进行函数调用。
这种方式在 Go 1.9 中开始支持,在 Go 1.10 开始写入 Go 的 specs。这个例子使用的是 Go master 分支的版本,可能是 Go 1.11。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

type M struct {

}

func (*M) f(n int) {
println("I;m M, with", n)
}

type IM interface {
f(n int)
}

func main() {
m := &M{}
IM.f(m, 7)
}
1
I;m M, with 7

此外还能使用匿名接口类型去调用函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

type M struct {

}

func (*M) f(n int) {
println("I;m M, with", n)
}

func main() {
m := &M{}
interface{f(n int)}.f(m, 7)
}

运行结果与上面的一段采用 IM 接口定义的例子是一样的。

5. 注入依赖

有时候一个对象在实例化的时候,它的一些成员方法的行为可能还没有确定,需要依赖外界注入。此时我们可以在对象类型定义中内嵌一个接口,然后在后期传入一个接口的实例来确定其行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

type BinaryOp interface {
Compute(a, b int) int
}

type ComputeNode struct {
x, y int
BinaryOp
}

func (node *ComputeNode) Result() int {
return node.BinaryOp.Compute(node.x, node.y)
}

type Add struct {}

func (*Add) Compute(a, b int) int {
return a + b
}

type Multi struct {}

func (*Multi) Compute(a, b int) int {
return a * b
}

func main() {
node := &ComputeNode{x:2, y:3}

node.BinaryOp = &Add{}
println(node.Result())

node.BinaryOp = &Multi{}
println(node.Result())
}
1
2
5
6

注意一定要记得传入接口的实例,在这个例子中如果不给 node 传入一个 BinaryOp 接口实例,那 node.BinaryOpnil,在调用 Compute 方法的时候就会发生异常。例如将上面的 main 函数稍作修改:

1
2
3
4
5
6
func main() {
node := &ComputeNode{x:2, y:3}

//node.BinaryOp = &Add{}
println(node.Result())
}
1
2
3
4
5
6
7
8
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x104d8d7]

goroutine 1 [running]:
main.(*ComputeNode).Result(...)
/Users/bef0rewind/Projects/net example/src/receiver_type/main/injection.go:13
main.main()
/Users/bef0rewind/Projects/net example/src/receiver_type/main/injection.go:32 +0x47

6. 内部机制

内部机制有一些细节。大体就是一个接口 i 包含两部分内容(指针),一个是接口代表的方法的集合,一个实现这个接口的具体对象;而一个对象 obj,它包含了自己的内存中的值,也能通过其类型获取到 obj 实现的方法集合。

将这两个概念记住,在实现一些模式的时候就会少很多心智负担。

7. 总结

Go 语言的这套基于 “duck typing” 的机制好不好,争论有很多。不过我一向对这些争论没有特别的倾向,至少理解其机制之后按照其设计思路来用还可以正常使用,而且里面没有复杂的概念和例外情形。

也许我的理解有偏差,但现在还没有发现什么矛盾的地方。

defer, panic and recover in Golang

1. 什么是异常处理

程序在执行过程中有可能出现异常状态,比如获取一个不再有效指针指向的内容、除零等。
一般语言都提供了异常处理机制来应对这些情形,例如 Java 的 try/catch/finally 机制(https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html)、
Python 的 try/raise/except/finally 机制(https://docs.python.org/3/tutorial/errors.html)等。

2. Go 语言中的异常处理机制

Go 语言中使用的是 defer/panic/recover 机制来处理异常。Go 语言官方博客的《Defer, Panic, and Recover》讲述了这个机制的具体应用方式。

还有一些其他教程对这个机制的使用方法、适用场景进行了进一步阐述:

如果搜索 “golang 异常处理”,类似的教程有很多。里面的核心思想大体就是:用 defer + recover 处理一个 panicdefer 结构要在 panic 触发之前被定义而且 recover 要直接在在 defer 结构定义的函数中被调用(而不是被直接调用或者在函数内部的其他函数中被调用)。

3. defer 语法糖的部分原理

在讲述 defer 机制的文章中,都会提到一个函数中多个 defer 结构执行的顺序和定义顺序是相反的,即后定义的 defer 结构总是先被执行。为什么会出现这样的情况?例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
func g(n int) {
println(n)
}

func h(str string) {
println(str)
}

func f() {
defer g(0)
defer h("h")
}

调用 f 输出为:

1
2
h
0

常见的函数调用流程为:

  • 将函数使用的参数压入栈
  • 执行函数指令
  • 函数执行结束返回到调用点

如果 defer 相关的代码也是这么执行的话,那么为什么不是: 0 入栈 - 执行 g - g 返回 - "h" 入栈 - 执行 h - h 返回 这个顺序呢?
按照这个顺序执行,调用 f 输出应该是 0h 前面符合预期。是不是 Go 语言中执行 defer 时采用了特殊的处理流程?

是,也不是。

太阳底下无新鲜事,defer 不过是一个语法糖,用来对一个函数 deferproc 进行包装。

1
2
3
4
// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval)

deferproc 创建一个延迟调用的函数,其参数为 siz (延迟调用的函数的参数占用的字节数量)和 fn(被延迟调用的函数本身)。
当 Go 程序的编译器遇到 defer f(),会将这条语句翻译为一条 deferproc 和一条 deferreturn
其中 deferproc 把被调用的函数及其参数挂载在 goroutine (Go 中的并发单元,协程)结构的一个链表上;
deferreturn 从链表上取下一个挂载的被延迟执行的函数,执行它。

如何使用技巧绕过 defer 关键字,模拟类似效果?
可以使用 linkname 方法来把 Go 语言运行时的一些关键函数导出,从而进行某些不常见的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
_ "runtime"
"unsafe"
)

type Eface struct {
_type uintptr
Data unsafe.Pointer
}

func EfaceOf(ep *interface{}) *Eface {
return (*Eface)(unsafe.Pointer(ep))
}

type Funcval struct {
fn uintptr
// variable-size, fn-specific data here
}

//go:linkname Deferproc runtime.deferproc
func Deferproc(siz int32, fn *Funcval)

//go:linkname Deferreturn runtime.deferreturn
func Deferreturn(arg0 uintptr)

func main() {
var f = func() {
println("hacked defer")
}
var fI interface{} = f

// Attach a defer struct to the current goroutine struct
Deferproc(0, (*Funcval)(EfaceOf(&fI).Data))

defer func() {
println("original defer")
}()

// Run a deferred function if there is one
Deferreturn(0)
}

这段代码会输出:

1
2
original defer
hacked defer

当然,如果是使用 defer 关键字,Go 语言的编译器会选择合适的位置插入 deferreturn 语句,而不是像上述代码中一样手动放在结束位置处。

4. recover 生效位置的设计原因推测

言归正传,panic 发生后,会根据函数调用顺序逐层上报,直到最后一层被抛出到系统导致崩溃或者被 recover 机制处理。
那么如果被 recover 处理,这个过程是怎么生效的?

很多教程中都提到 recover 一定要在 defer 声明的函数里面(既不是这个函数本身也不能是函数里面的其他函数里面)才能正确处理当前的 panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// case 1, not work
defer recover()

// case 2, not work
defer func() {
func() {
recover()
}
}()

// case 3, work
defer func() {
recover()
}()

为什么呢?

先不考虑实现,先从理念上分析一下。

  1. defer 直接作用于 recover():无法根据 recover() 的返回值来进行不同类型的 panic 处理
  2. 在被 defer 作用的函数内部的函数 g 中使用 recover():如果 g 是一个第三方库的函数,无法保证其中没有未知的 recover 意外处理了系统中的 panic

因此事实上也只能通过这样的约束来使这个异常处理机制看上去直观易处理一些。当然通过对 Go 编译器进行修改,还是有办法使得上面三种情况下 recover 都可以中断 panic 向上层传递过程的。

此外,由于被 defer 处理的函数被挂载在 goroutine 结构的一个链表上,因此当 panic 发生时,可以直接从这个链表上取下被延迟执行的函数一个个执行。
这也是 recover 要放在 deferred function 中的原因,因为这些函数是肯定可以执行到的。

5. 总结

不能说 Go 中这个异常处理机制有多高明,基本上属于现代语言标配。了解更多背后的原理,在使用时可以更坚定一些。

此外,最近看到一本书《最好的告别》(https://book.douban.com/subject/26576861/)。

Being Mortal

豆瓣上的介绍:

当独立、自助的生活不能再维持时,我们该怎么办?在生命临近终点的时刻,我们该和医生谈些什么?应该如何优雅地跨越生命的终点?对于这些问题,大多数人缺少清晰的观念,而只是把命运交由医学、技术和陌生人来掌控。影响世界的医生阿图•葛文德结合其多年的外科医生经验与流畅的文笔,讲述了一个个伤感而发人深省的故事,对在21世纪变老意味着什么进行了清醒、深入的探索。

defer / finally 这些关键字让我们可以控制函数退出时的行为,但是我们自身呢?也许考虑这些问题可以让我们自身活得有意义一些。

推荐大家看一下。

Useful Commands

Convert images to a video

1
ffmpeg -r 30 -start_number 3455 -i _IMG%d.jpg -s 960X600 -pix_fmt yuv420p 30fps-960.mov
  • -r 30: 30 frames per second
  • -s 960X600: resolution
  • -pix_fmt yuv420p: for OsX

youtube-dl video and extract audio file

youtube-dl --proxy socks5://127.0.0.1:1080 -x --audio-format mp3 youtube-url

virtualenvwrapper

  • WORKON_HOME: which directory your environments are created in
  • /usr/local/bin/virtualenvwrapper.sh: default location for its configuration file
  • mkvirtualenv test --python=python3: make a virtual environment ‘test’ with python3
  • rmvirtualenv test: remove a virtual environment ‘test’
  • workon test3 or lsvirtualenv -b test3: activate a virtual environment ‘test’
  • deactivate: exit current environment
  • more details: search engine
    • how to avoid globa packages
    • how to copy an environment

node && npm

npm complains: Error: Cannot find module 'process-nextick-args'

Uninstall node, brew uninstall node, then by this stackoverflow post:

1
2
3
sudo rm -rf /usr/local/bin/npm /usr/local/share/man/man1/node* /usr/local/lib/dtrace/node.d ~/.npm ~/.node-gyp 
sudo rm -rf /opt/local/bin/node /opt/local/include/node /opt/local/lib/node_modules
sudo rm -rf /usr/local/bin/npm /usr/local/share/man/man1/node.1 /usr/local/lib/dtrace/node.d

Just delete something, then brew install npm.

delve (dlv) tips

  • funcs [regexp] : get function list
  • call : call a function (in a newer a go version, dlv should be installed in the newer go version too)

ss

ssserver -c /etc/shadowsocks/config.json

sslocal and ssserver are all from apt-get install shadowsocks.

电池人生

电池,一般狭义上的定义是将本身储存的化学能转成电能的装置,广义的定义为将预先储存起的能量转化为可供外用电能的装置。因此,像太阳能电池只有转化而无储存功能的装置不算是电池。其他名称有电瓶、电芯,而中文池及瓶也有储存作用之意。

《维基百科》

5 月份上海校友会来杭州参观,我带队去云栖景区玩,后来大家一起去了阿里西溪园区。园区很漂亮,中间有一块地被高墙围起,据说马云在那里开董事会。后面有个讨论环节,那时我已经昏昏欲睡了,只记得那个阿里 P11 说今后公司要拓展的方向是娱乐和健康。

娱乐和健康都是人生活的必需品。

最近认识一些朋友去今日头条工作了。这个公司的应用通过算法构建消息流推送到人们的移动终端。在信息泛滥的今天,可以永远向下滑动手指,看到无尽的“新”闻。可算法知道,这些新闻的类型是你喜欢的,同样的主题是你的老朋友了。

当然这样的应用通常不需要你付费,因为你就是它们的商品。一次次的滑动中除了那些通过视网膜输入的新闻还有各式各样的广告,都是基于你的“兴趣”进行筛选后的结果。它们攫取你的时间卖给了肯花钱展示商品的人,赚到了差价。

以前看黑客帝国的时候,总是不明白 Matrix 那么发达,却还是需要将人关在营养仓里当做电池使用。人是消耗能量的啊!最近才逐渐有所领悟,人体不一定可以拿来产生能量。人作为进化的产物,智能依赖生物结构,Matrix 可以将一个个的人作为基础的计算组件,用来支撑虚拟世界中的各种计算。现在我们使用人工智能也是如此,用来识别人脸、用来预测天气 ……

《海伯利安》中有一个设定,机器智能帮助人类构建了可以在宇宙各处穿梭的跃迁节点,而这种技术远远超出了人类的理解范围。人们以为这是机器智能回馈给它们的“造物主”的礼物,但实际上这是一个陷阱。人穿梭过跃迁节点从另一侧出来并不是瞬间完成的。在节点中度过的无法感知到的时间中,人的大脑的计算能力被机器智能利用,完成了自身的不断进化,来追求它们的终极目标。

在阿西莫夫的经典作品《我,机器人》改编的电影中,威尔•史密斯扮演的黑人警探戴尔•斯普纳不相信人类能和机器人和平共处,故事的各种冲突也从此展开。这部作品的底线是机器人还遵守“机器人三定律”,虽然是被以某种人类意想不到的方式施行的。现实中,我们能对机器人有所期待吗?我们能对创造机器人的人有所期待吗?

总是有人对新的技术保持怀疑 —— 有些人因为未知而恐惧,有些人因为远见而忧虑,只有中间的人享受着生活。

有时候想人的一生好短暂,也就一百年。昨天看到淡豹的一篇文章《近日新闻有感》,中间有一大段对日常种种琐事的描述,读的过程中仿佛头被浸在水中。究竟是什么(强大的)力量在驱使我们生活?

在我看来这种力量里又加入了一方势力,有些人在尽最大努力使用着智能算法来侵占人的生命。以前他们也这样做,通过编辑人们喜欢的花边新闻和轶事来吸引人们注意,不过那种方式毕竟低效。现在算法可以让人们通过滑动手指,自己戴上镣铐,出卖生命。

小的时候,有段时间我很喜欢四驱车,当然是在看了《四驱小子》之后。终于有了一辆(无影剑)后,我发现了一个现实的问题:这车跑起来很费电池,于是又攒钱买充电电池。最终电池的能量变成了车在赛道上前进的动力以及我的回忆。

我对四驱车的欲望肯定是来自动画片,后来我弟弟那时候买的就是悠悠球了。人的大脑是可以被外界信息影响的,现代科技也会以某种方式对人的大脑进行编程。现在人脑运行的机制还不清楚,如果可行的话,那些广告商肯定想通过某种方式对你的大脑进行编程,让你看一遍广告就下单购物。

我前段时间在想:如果总是接受这样被算法(你的手指)选择过的信息,会变傻吗?至少我体会过那种无效信息过载的时刻,就是大脑没什么感觉了。

在这个过程中我们到底付出了什么呢?我们的时间,我们的未来。

随着时间的推移,人的未来就逐渐坍缩,直到生命终点,也就没有未来了。每一秒过去,我们的未来就少了几分可能性。

如果明天不值得期待,就继续燃烧自己吧。

Upgrade DigitalOcean's Ubuntu 17.04 to LTS version in 2018-06

What happened?

I found my proxy for accessing some websites stopped working today, so I had to change my VPS’s IP address.
After some trials, everything seemed OK and I started watching a skiing video made by NorthFace on Youtube.

Emmmm, I noticed 12 packages needed to be updated.
Well, I typed sudo apt-get update and got messages like this (I didn’t save the error messages then):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  404  Not Found [IP: 91.189.91.23 80]
Ign:10 http://us.archive.ubuntu.com/ubuntu zesty-backports InRelease
Err:11 http://us.archive.ubuntu.com/ubuntu zesty Release
404 Not Found [IP: 91.189.91.26 80]
Err:12 http://us.archive.ubuntu.com/ubuntu zesty-updates Release
404 Not Found [IP: 91.189.91.26 80]
Err:13 http://us.archive.ubuntu.com/ubuntu zesty-backports Release
404 Not Found [IP: 91.189.91.26 80]
Reading package lists... Done
E: The repository 'http://security.ubuntu.com/ubuntu zesty-security Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty-updates Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: The repository 'http://us.archive.ubuntu.com/ubuntu zesty-backports Release' does no longer have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

Wow, It was the first time I encountered such things.
After some digging, I realized it was because Ubuntu 17.04 (zesty) was not supported any longer.

According to https://wiki.ubuntu.com/ZestyZapus/ReleaseNotes

Ubuntu 17.04 will be supported for 9 months until January 2018. If you need Long Term Support, it is recommended you use Ubuntu 16.04 LTS instead.

It means my current release version (Ubuntu 17.04) won’t receive any updates in the future.

release-end-of-life-date

How to upgrade

I was struggled to reveal some steps to upgrade my Ubuntu 17.04 (End-of-Life) to 18.04 (LTS version).

The official guide to upgrade from an Ubuntu release which reaches its “end of life” is: https://help.ubuntu.com/community/EOLUpgrades

1. Update sources.list

The path of sources.list is /etc/apt/sources.list.

1
2
3
4
5
6
7
8
## EOL upgrade sources.list
# Required
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-updates main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-security main restricted universe multiverse

# Optional
#deb http://old-releases.ubuntu.com/ubuntu/ CODENAME-backports main restricted universe multiverse

In my case, CODENAME should be replaced by zesty.

2. Upgrade

Try apt-get upgrade, and I got:

1
2
3
dpkg: dependency problems prevent configuration of ubuntu-standard:
ubuntu-standard depends on libpam-systemd; however:
Package libpam-systemd:amd64 is not configured yet.

By some method suggested by stackoverflowers, I finally got over it by these commands:

1
2
sudo dpkg --force-all -P libpam-systemd
sudo apt-get -f install libpam-systemd

3. Dist Upgrade

These are some normal steps for an upgrade.

1
2
sudo apt-get dist-upgrade
sudo do-release-upgrade

If you’re unlucky like me, you will not upgrade to a newer release completely.
You may see:

1
Package grub-efi-amd64 is not configured yet.

According to a question asked by someone, you can try this:

1
2
sudo dpkg --force-all -P grub-efi-amd64
sudo dpkg --force-all -P grub-efi-amd64-signed

Execute sudo apt-get update and sudo apt-get upgrade.

4. Thoughts

I first upgraded to Ubuntu 17.10 then Ubuntu 18.04.

To see if I have done the right things, I check the release version by sudo lsb_release -a:

1
2
3
4
Distributor ID:    Ubuntu
Description: Ubuntu 18.04 LTS
Release: 18.04
Codename: bionic

Is it the DigitalOcean’s customized release makes the upgrade so complex for a non-expert programmer or the Ubuntu release itself?

But apparently, I’m not the only one who is confused by the error messages and asks questions on the web. Luckily I’ve found helping answers I need.

The commands with words like force or -f make me feel anxious.

These violent delights have violent ends.
— by Shakespeare

Dolores

Anyway, it works.