Lamport 时钟
定义a->b为事件a发生在事件b之前,进程为Pi,进程对应本地逻辑时钟为Ci,Ci(a)代表进程P发生事件a时的逻辑时钟值,则Ci的更新如下
- 进程Pi每发生事件,Ci加1
- 进程Pi每发送进程Pj消息,Pj根据Pi传递过来的Ci与自己的Cj比较取更大值加1
那么显然对于a->b,C(a)<C(b)。a、b是顺序发生的,那么在事件b所在进程接受之后计算得到的时钟一定大于事件a所在进程的时钟
但C(a)<C(b)不一定a->b,因为a、b可能是并行发生的。a所在的进程并没有在a完成之后顺序执行调用事件b
type LamportsProcess struct {
mu *sync.Mutex
id int
clock int
}
// 产生事件时,时间戳加1
func (l *LamportsProcess) event() {
l.mu.Lock()
defer l.mu.Unlock()
l.clock++
}
// 发送消息时,时间戳加1
// 接受者的时间戳取自己的时间戳和接受者的时间戳的最大值加1
func (l *LamportsProcess) send(p *LamportsProcess) {
l.mu.Lock()
defer l.mu.Unlock()
l.clock++
p.receive(l)
}
// 接受消息时,时间戳取自己的时间戳和发送者的时间戳的最大值加1
func (l *LamportsProcess) receive(p *LamportsProcess) {
l.mu.Lock()
defer l.mu.Unlock()
l.clock = maxInt(l.clock, p.clock) + 1
}
func maxInt(x, y int) int {
if x > y {
return x
}
return y
}
向量时钟
向量时钟不仅同步本进程的时钟值,而且还同步已知的其他进程时钟值
以此去实现在Ci>Cj情况下,能推导出a->b
分布式系统中每个进程Pi保存一个本地逻辑时钟向量值VC(i),VCi(j)代表进程Pi知道的进程Pj的本地逻辑时钟值
- 初始化VCi向量为[0,…]
- 进程Pi每发生一次事件,VCi[i]加一
- 进程Pi给进程Pj发送消息,需带上自己的向量时钟VCi
- 进程Pj接收消息后
- 更新VCj向量为VCj向量与VCi向量中的最大值
- 将VCj中自己对应时钟值加一,也就是VCj[j]加一
向量时钟里向量时间戳是一种偏序关系。
如果向量时间戳VC(i)中所有时间戳都大于对应向量时间戳VC(j),那么VC(i)>VC(j)
如果向量时间戳VCi中所有时间戳都等于对应向量时间戳VC(j),那么VC(i)=VC(j)
否则则不能比较,两向量为并发
从以上算法可以很容易地得出下面两个结论:
- 同一个进程内的两个事件a和b,如果 a → b,那么 VCi (a) < VCi (b)。
- a是Pi进程的消息发送事件,b是Pj进程该消息的接收事件,那么 VCi (a) < VCj (b)。
type VectorProcess struct {
mu *sync.Mutex
// 进程id
id int
// 向量时间戳
vClock []int
}
func NewVectorProcess(id, totalProc int) *VectorProcess {
vClock := make([]int, totalProc)
return &VectorProcess{
mu: &sync.Mutex{},
id: id,
vClock: vClock,
}
}
// 产生事件时,时间戳加1
func (p *VectorProcess) event() {
p.mu.Lock()
defer p.mu.Unlock()
p.vClock[p.id]++
}
// 发送消息时,时间戳加1
func (p *VectorProcess) send(q *VectorProcess) {
p.mu.Lock()
defer p.mu.Unlock()
p.vClock[p.id]++
q.receive(p)
}
// 接受消息时,时间戳取自己的时间戳和发送者的时间戳的最大值加1
func (p *VectorProcess) receive(q *VectorProcess) {
p.mu.Lock()
q.mu.Lock()
defer p.mu.Unlock()
defer q.mu.Unlock()
for i := 0; i < len(p.vClock); i++ {
p.vClock[i] = maxInt(p.vClock[i], q.vClock[i])
}
p.vClock[p.id]++
}
证明
那么如何证明VC(i) < VC(j) 可以推导a → b呢?
如果事件a、b在同一个进程,那么肯定a->b
如果事件a、b分别在进程Pi和Pj中,那么设c为发送a更新事件,d为接收a更新事件。设VC(i)=[m, n],VC(j)=[s, t]
在VC(i)<VC(j)情况下,m<=s,所以在事件a发生之后,事件b发生之前Pi一定向Pj发送了消息,那么接受a更新的事件d一定不晚于b,因此
- 如果a=c且d=b,那么显然a->b
- 如果a=c且d->b,那么a->b
- 如果a->c且d=b,那么a->b
- 如果a>c且d->b,那么a->b
综上所述,可得VC(i)<VC(j)的情况下,a->b
Ref
- https://yang.observer/2020/09/12/vector-clock/