实践背景
本次实践的背景依然MIT的6.824课程,在课程中lab2需要实现Raft算法,Raft算法的原理细节在我的另外一篇博客中,在这里就不再赘述了。
Raft中Leader Election
在Raft算法中,当一个节点变为candidate节点之后,其他的节点需要对其进行投票,当其获得超过半数票数时即当选,反之落选。下面将对该过程进行简单的模拟实践。
实践思路
1.首先向其他节点发送投票请求;
2.根据投票请求的结果进行票数计算;
3.根据票数的结果,得出投票的结果。
那么在第一个步骤中,一定是并发的同时向所有节点发送投票请求,而非串行的发送请求。所以这里需要用到多线程协同。其次在票数统计时,会涉及到用几个变量来记录票数的情况,在多线程情况下操作这些变量则需要lock来保证线程安全。
具体代码
根据以上思想,完成简易的实现代码如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
count := 0
finshed := 0
var mu sync.Mutex
rand.Seed(time.Now().UnixNano())
//假设一共10个节点
for i := 0; i< 10 ; i++ {
go func() {
vote := requestVote()
mu.Lock()
defer mu.Unlock()
if vote {
count++
}
finshed++
}()
}
for {
mu.Lock()
if count >5 || finshed == 10 {
break
}
mu.Unlock()
}
if count > 5 {
fmt.Println("received major voted!")
}else {
fmt.Println("failed!")
}
}
func requestVote() bool {
//随机模拟投票时间和投票结果
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return rand.Int() % 2 == 0
}
这里利用rand随机包来实现请求投票requestVote的模拟,首先等待一个随机事件,然后再返回一个随机的bool类型。值得注意的是,需要对count以及finshed这两个变量的操作都加锁,保证线程安全。
发现问题
上述代码看似没有问题,实则存在一个性能隐患。本身我们在完成这段代码时,并未考虑性能问题,只是完成具体的功能即可。但是,请看下面这段代码:
for {
mu.Lock()
if count >5 || finshed == 10 {
break
}
mu.Unlock()
}
这里的死循环,可能会导致cpu的某个核的利用率达到了100%,为避免这种情况,可以让其休眠一定时间再重新拉起,使得cpu资源可以被释放,如下:
for {
mu.Lock()
if count >5 || finshed == 10 {
break
}
mu.Unlock()
time.sleep(5 * time.Seconed)
}
继续优化
上述问题虽然解决了,但是依然不够优化,在多个线程需要根据共享变量的结果来执行某些条件操作时,可以用到条件变量,如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
count := 0
finshed := 0
var mu sync.Mutex
cond := sync.NewCond(&mu) //设置条件变量,提高程序性能
rand.Seed(time.Now().UnixNano())
//假设一共10个节点
for i := 0; i< 10 ; i++ {
go func() {
vote := requestVote()
mu.Lock()
defer mu.Unlock()
if vote {
count++
}
finshed++
cond.Broadcast()//唤醒所有在wait的线程
}()
}
mu.Lock()
for count < 5 && finshed != 10 {
cond.Wait()//线程挂起等待
}
if count > 5 {
fmt.Println("received major voted!")
}else {
fmt.Println("failed!")
}
mu.Unlock()
}
func requestVote() bool {
//随机模拟投票时间和投票结果
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return rand.Int() % 2 == 0
}