Blog

判断某个类型是否实现了Lock接口

Content #

下面的代码就是确定什么类型会被分析,其实就是实现了 Lock/Unlock 两个方法的 Locker 接口:

var lockerType *types.Interface
// Construct a sync.Locker interface type.
func init() {
    nullary := types.NewSignature(nil, nil, nil, false) // func()
    methods := []*types.Func{
        types.NewFunc(token.NoPos, nil, "Lock", nullary),
        types.NewFunc(token.NoPos, nil, "Unlock", nullary),
    }
    lockerType = types.NewInterface(methods, nil).Complete()
}

Viewpoints #

From #

03|Mutex:4种易错场景大盘点

先行后思

Content #

仪式习惯的持久力来源于精力节约的本质。对此,哲学家怀特海是如何阐述的?

“我们不该培养先思后行的习惯,”哲学家怀特海于1911年写道。“反过来才是正确的。当人们不靠思索便能做出的行为越来越多,文明才得以进化。”意愿和自律推着我们行动,而悉心养成的仪式习惯会吸引我们做出行动,一旦做不到还会产生不适感,例如刷牙,沐浴,早晨吻别爱人,观看孩子的足球比赛,或者周末给父母打电话。如果我们需要一个能够持久的新行为,肯定不能花太多精力来维持它。

主动性和自律比我们想象的更加稀缺,因此我们必须选择性地取用。即使很小的自控行为都会耗尽储备,这次主动运用精力意味着下次可用的精力减少。清晰的事实是,我们每天只有非常有限的精力来进行自控。

From #

养成儿童自得其乐性格

Content #

养成儿童自得其乐性格的一大重要因素是什么?少了它,就很难长时间放松自我,真正体会心流。

无条件地接纳对儿童尤其重要,如果父母威胁孩子,不能实现要求就收回对他的爱,孩子游戏的天性就会逐渐被长期焦虑所取代。但如果孩子知道父母无条件地为他的幸福奉献,他就能无所畏惧地去探索这个世界;否则他就只好抽出一部分精神能量来保护自己,这样他能自由运用的精神能量就少了。早期精神上的安全感,很可能是养成儿童自得其乐性格的一大要素,少了它,就很难长时间放松自我,真正体会心流。

From #

心流

端口访问指令in和out

Content #

in指令单字节形式:

in al, dx ;机器码0xEC
in ax, dx ;机器码0xED

指令的目的操作数必须是AL或AX。之所以机器码只有一个字节,是因为不允许使用其它通用寄存器。

in指令的双字节形式:

in al, 0xf0 ;机器码0xE4F0
in ax, 0x03 ;机器码0xE503

前一字节是操作码0xE4或0xE5,分别指示8位或16位端口访问。后一字节为立即数,指示端口号。

out指令与in指令正好相反,目的操作数可以是8位立即数或DX,源操作数必须是 AL或AX。

out 0x37, al ;8位
out 0xf5, ax ;16位
out dx, al ;8位,端口号在dl中
out dx, ax ;16位,端口号在dx中

From #

“谁申请,谁释放”的原则

Content #

Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至今。

这就带来了一个有趣而危险的功能。为什么这么说呢?

其它 goroutine 可以强制释放锁,这是一个非常危险的操作,因为在临界区的 goroutine 可能不知道锁已经被释放了,还会继续执行临界区的业务操作,这可能会带来意想不到的结果,因为这个 goroutine 还以为自己持有锁呢,有可能导致 data race 问题。

所以,我们在使用 Mutex 的时候,必须要保证 goroutine 尽可能不去释放自己未持有的锁,一定要遵循“谁申请,谁释放”的原则。在真实的实践中,我们使用互斥锁的时候,很少在一个方法中单独申请锁,而在另外一个方法中单独释放锁,一般都会在同一个方法中获取锁和释放锁。

如果你接触过其它语言(比如 Java 语言)的互斥锁的实现,就会发现这一点和其它语言的互斥锁不同,所以,如果是从其它语言转到 Go 语言开发的同学,一定要注意。

Viewpoints #

From #

02 | Mutex:庖丁解牛看实现

Mutex的架构演进

Content #

Mutex 的架构演进分成了四个阶段,下面给你画了一张图来说明。

“初版”的 Mutex 使用一个 flag 来表示锁是否被持有,实现比较简单;后来照顾到新来的 goroutine,所以会让新的 goroutine 也尽可能地先获取到锁,这是第二个阶段,我把它叫作“给新人机会”;那么,接下来就是第三阶段“多给些机会”,照顾新来的和被唤醒的 goroutine;但是这样会带来饥饿问题,所以目前又加入了饥饿的解决方案,也就是第四阶段“解决饥饿”。

Viewpoints #

From #

02 | Mutex:庖丁解牛看实现

信号处理函数的调用过程

Content #

信号处理函数的调用过程是由来自操作系统内核的软中断触发的,因此,这个过程与我们平时见到的,通过 call 指令进行的函数调用过程并不相同。

信号处理的例子:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void sigHandler(int sig) {
  printf("Signal %d catched!\n", sig);
  exit(sig);
}
int main(void) {
  signal(SIGFPE, sigHandler);
  int x = 10;
  int y = 0;
  printf("%d", x / y);
}

围绕信号的基本交互逻辑可以被粗略地描述为以下几个步骤:

  1. CPU 执行除法指令 idiv;
  2. 发现除零异常,CPU 暂停当前程序运行,并将控制权转交给操作系统;
  3. 操作系统将信号 SIGFPE 发送给出错的程序;
  4. 操作系统根据情况执行相应的信号处理程序(函数);
  5. 信号处理程序执行完毕后,若程序未退出,则将程序执行恢复到之前的中断点,即 CPU 会重新执行 idiv 指令。

Viewpoints #

From #

15|标准库:信号与操作系统软中断有什么关系?

同步原语的适用场景

Content #

  1. 共享资源。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要 Mutex、RWMutex 这样的并发原语来保护。
  2. 任务编排。需要 goroutine 按照一定的规律执行,而 goroutine 之间有相互等待或者依赖的顺序关系,我们常常使用 WaitGroup 或者 Channel 来实现。
  3. 消息传递。信息交流以及不同的 goroutine 之间的线程安全的数据交流,常常使用 Channel 来实现。

Viewpoints #

From #

01 | Mutex:如何解决资源并发访问问题?

select语句与default分支

Content #

Go语言中select语句中有无default语句对select执行时是否阻塞会产生什么样的影响?

仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足选择条件,那么默认分支就会被执行。如果这时没有默认分支,那么select语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select语句(或者说它所在的 goroutine)就会被唤醒,这个候选分支就会被执行。

From #

unsafe基本用法

合理的用法 #

// The cases is suitable for unsafe
type MyInt int

//合理的类型转换
func TestConvert(t *testing.T) {
        a := []int{1, 2, 3, 4}
        b := *(*[]MyInt)(unsafe.Pointer(&a))
        t.Log(b)
}

From #