goland go module 找不到包_MIT6.824 - Go语法准备

在MIT6.824 Go项目中,很多时候大家都知道逻辑上如何实现,但具体到代码中却不知从何做起(尤其对于写惯了Python的同学(‵﹏′))。为了完成该项目,需要掌握的Go相关知识不多,这里简单做一个总结。

GoLand

建议使用GoLand进行代码的编写和调试,有丰富的断点调试、函数提醒,自动补全等等对初学者非常实用的功能。初次使用GoLand,切记在Settings中设置GOROOT和GOPATH环境变量

  1. 如何设置调试/运行环境?

为开发和测试方便,可为每一个Lab单独设置一个Go Test Configurations。如图,1)直接编译运行3A所有test cases;2)调试运行;3)你的test_test.go脚本所在package;4)在go test运行时,只要函数名包含该pattern关键词都会运行

5ff23a34aa41a06eb85aa4bf3278490e.png

2. Golang没有传统意义上的try-catch机制,但可通过golang提供了recover机制进行断点调试

func A() { 
    defer func() { 
        if r := recover(); r != nil { 
            fmt.Println("Recovered in f", r) // 断点设于此,当发生error的时,GoLand可保存error发生时的现场
         } 
    } () 
    // 正常的函数代码逻辑
}

输出

在该项目中,各种并发、crash等,光靠断点调试是远远不够的,最有力的工具还是print!!

// 输出一个字符串
fmt.Println("error")

// format后输出一个字符串
fmt.Printf("Index: %dn", lastAppliedIndex)

// format后赋值一个字符串
a := fmt.Sprintf("Index: %d", lastAppliedIndex)

// 数字 => 字符串
s := strconv.Itoa(-42)

// 字符串 => 数字
i, err := strconv.Atoi("-42")
if err == nil {
    fmt.Println(i)
}

// 在多节点中,给每个goroutine加一个identifier
func uuid4() string {
    b := make([]byte, 16)
    _, err := craned.Read(b)
    if err != nil {
        fmt.Println("Error: ", err)
        return "no-uuid"
    }

    return fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
uuid := uuid4()
fmt.Printf("[%s]Index: %dn", uuid, lastAppliedIndex)

Channel

Go通过 channel 进行协程之间的通信来实现数据共享,在Lab1, 2, 3都会用到channel。channel主要有两个操作:1)chan<- data 阻塞写数据;<-chan 阻塞读数据(其也有非阻塞的方案,不过此项目中用不到:Go by Example: Non-Blocking Channel Operations)。下面是一个测试case,有助理解:

func A() {
    c1 := make(chan string)
    go func() { time.Sleep(5 * time.Second); c1 <- "111" } ()

    v1 := <- c1
    fmt.Println(v1)

    c2 := make(chan string)
    go func() { v2 := <- c2; fmt.Println(v2) } ()

    time.Sleep(5 * time.Second)
    c2 <- "222"
}

利用阻塞读写这一性质,我们可利用一个单独的goroutine作为守护进程来监听来自applyCh的消息。如下,该goroutine是一个死循环,当没有通知的时候,会一直阻塞在那里,而非一直消耗CPU运行。

go func() {
    for {
        select {
            case msg := <-kv.applyCh:
                // do something with msg
        }
    }
} ()

WaitGroup

在该项目中有一种情况:同时执行n多goroutine任务,等待所有goroutine执行结束后,返回结果,这时候可以使用WaitGroup,我们主要涉及三个方法: wg.Add(), wg.Done(), wg.wait()

  • wg.Add() 对waitGroup counter对象+1,通常当一个goroutine运行前执行
  • wg.Done() 对waitGroup counter对象-1,通常当一个goroutine结束后执行
  • wg.Wait() 一直阻塞,直到waitGroup counter对象的值为0
func A() {
    var wg sync.WaitGroup
    for i:=0; i<10; i++ {
        wg.Add(1)
        go func B() {
            defer func() { wg.Done() } ()
            // do some stuff
        } ()
    }
    
    wg.Wait()
    return
}

Timer

在实现Lab2的时候,涉及到Timer的使用,比如心跳包和election timeout的控制主要实现的功能即:在特定时间后,发送一个通知给Timer chan。以心跳包举例,我们可以每隔1s发送一个消息给heartBeatTimer chan,然后我们使用一个goroutine监控heartBeatTimer chan。在此,主要涉及:timer.Stop(), timer.Reset(), timer.C

  • timer.Stop() 暂时停止该timer,比如对于follower来说我们不需要heartBeat
  • timer.Reset(duration) 重置时间,时间从头重新计算,这对election尤其重要。
  • timer.C 该timer的chan对象
// heartBeat timer
go func() {
    for {
        select {
            case <- rf.heartBeatTimer.C:
                rf.heartBeatTimer.Reset(1 * time.Second)
        }
    }
} ()

Lock

该项目中大量使用了goroutine,因此会面临很多race condition。比如,修改rf.currentTerm的时候。在此项目中,已帮我们封装好Lock,无需过多关心细节,更多需要考虑的是:什么时候,什么地方应该加lock,具体可以参考: http://mpaxos.com/teaching/ds/20fa/labs/raft-locking.txt

rf.mu.Lock()
  // modify some shared data
rf.mu.Unlock()

版权声明:本文为weixin_29649047原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。