MIT6.824 Lab 踩坑记录
洪笳淏 Lv4

Lab1

go plugin

踩坑场景

  项目的初始化就是将 Map 和 Reduce 方法分别打包成插件的形式供 worker 端调用。这样设计的理由是测试用例中包含了不同的 Map 和 Reduce 方法,要通过所有的测试用例,使用插件的形式更加自由。
  但是在启动项目的一开始就报错:plugin was built with a different version of package runtime。在网上查了很久,只能知道是作为 plugin 的插件文件和调用这个插件的文件编译环境不同导致的。但是问题是我都是在本地编译的,怎么还存在这个问题呢?

解决方案

  无他,重新装了 go 的环境。。。成功解决该问题。巨 tm 魔幻。
  另外,mr 包下的任何改动都需要重新 go build --plugin,否则 worker 无法继续使用这个插件编译。

建议

  go plugin 目前还是一个不成熟的方案,受限因素太多,可能会出现各种莫名其妙的问题,建议之后自己的开发中还是避免是用 go plugin。

for-select 中的break、continue、return

踩坑场景

  在 worker 内部实现 map task 的时候,写了一个外层的 for 函数,里面是一个 select,case 1 是当出现超时(10s),那么就直接返回 false,否则就是 default 执行操作;当读到文件末尾 EOF时,完成操作后需要 break 跳出 for 循环。此时就出现了问题:break只能跳出select,无法跳出for,导致出现死循环。

解决方案

方案一:标签
看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func TestBreak(t *testing.T) {
tick := time.Tick(time.Second)
//FOR是标签
FOR:
for {
select {
case t := <-tick:
fmt.Println(t)
//break出FOR标签标识的代码
break FOR
}
}
fmt.Println("end")
}

方案二:goto

1
2
3
4
5
6
7
8
9
10
11
12
13
func TestBreak(t *testing.T) {
tick := time.Tick(time.Second)
for {
select {
case t := <-tick:
fmt.Println(t)
//跳到指定位置
goto END
}
}
END:
fmt.Println("end")
}

  这两种方式都能够成功跳出 for 循环,我选择了方案二。

continue:单独在select中是不能使用continue,会编译错误,只能用在for-select中。 continue的语义就类似for中的语义,select后的代码不会被执行到。
return:和函数中的return一样,跳出select,和for,后续代码都不执行

读取文件

踩坑场景

  Map 任务需要打开文件后统计单词出现频率,此时有两种读文件的方式。第一种是打开文件后逐行读取,第二种是打开文件后直接全部加载到内存中。第一种方式是用时间换空间的做法,在文件是个超大型文件,但是内存不够的情况下只能使用这种方法,唯一的问题就是很慢,IO 操作相当密集,很影响速度;第二种方式需要占用较大的内存,但是只需要一次性将文件全部加载到内存中即可,时间上会快很多。

解决方案

  在本 lab 的场景下,每个 job 只有 10s 的时间处理文件,那么显然应该采用第二种方式。我一开始就是采用的逐行读取的方式,发现有的文件在单 worker 运算时甚至完全无法在 10s 内统计完。
  同时还采用了临时文件的方式,临时文件会默认保存在系统的/tmp 路径下,如果要放在当前目录下需要指定,每个临时文件的末尾会带上一串随机的数字,可以在命名的时候做一些约定,方便之后的处理。
  对文件的命名带上了递增的 jobId,那么就避免了冲突问题。

建议

  • 因为命名规则可能存在缺陷,采用临时文件的形式可以避免任务失败后,任务被其他的 worker 接管造成的文件冲突;
  • 还可以通过约定命名规则,比如带上自增的 JobId,那么在之后的流程中只会对成功的 JobId 的文件进行处理。

[ ]chan int

踩坑场景

  在 Coordinator 结构体中声明了一个切片:workerDone []chan int,用于接收对应 worker 在规定时间内完成任务的信号。一开始只是建一个固定长度的切片:workerDone := make([]chan int, 20),在一个新的 worker 建立连接获得工号后如果超出长度,那么就再扩容 20 个位置。可是忽略了了一个事实,这样建立的切片,每个元素其实是 nil,所以一直不能正确的传递信号。

解决方案

要通过下面的方式:

1
c.workerDone[workerNum] = make(chan int)

  这样才能正确的完成对应工号的初始化。

  • Post title:MIT6.824 Lab 踩坑记录
  • Post author:洪笳淏
  • Create time:2022-04-06 12:40:00
  • Post link:https://jiahaohong1997.github.io/2022/04/06/MIT6.824 Lab 踩坑记录/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments