Mit 6.5840(原 6.824) lab 1 MapReduce的设计与实现(golang编写基于chan通信,纯原创)

1 实现思路

lab1基于RPC调用

由一个coordinator与一个或者多个Worker构成,下图中的Master由coordinator替代
在这里插入图片描述

lab1中的worker是一个个独立的进程,这些进程轮询调用coordinator从而获取任务。
由于并未区分Mapper和Reducer所以我们的策略是,先将所有的Map任务全部进行完,进行最终的归约(Reduce)

2 实现难点

  1. coordinator需要实现超时控制
  2. 保证线程安全
  3. 对性能的优化

3 难点攻坚

对于难点1我的实现思路是当coordinator分配任务的时候,开一个go routine负责监听,在go routine当中使用select case监听一个chan

worker任务结束后会请求coordinator的一个方法告知coordinator任务已经完成,在方法当中向chan中传一个消息(Msg) 告知go routine任务完成而go routine可以监听在固定时间内是否接受到信息如果超时(本lab为10s),可触发time.After()处理相关逻辑,比如将任务重新放回分配队列当中

难点1有个地方需要考虑,就是如果go routine认为已经超时,将任务重新放到可分配队列当中

而在这之后,worker才告知coordinator任务完成,这时候如何处理?

我的策略是,所有的task(map和reduce)都维护一个TaskId,map和reduce都维护一个HashSet(哈希表),用来标记这个TaskId是否超时,超时则不处理

难点2 加锁
难点3 见代码,使用队列,以及归并的思路统计数据

代码实现

这里给出rpc.go   worker.go   coordinator.go的代码

rpc.go

package mr

//
// RPC definitions.
//
// remember to capitalize all names.
//

import (
	"os"
	"strconv"
)

//
// example to show how to declare the arguments
// and reply for an RPC.
//

type ExampleArgs struct {
	X int
}

type ExampleReply struct {
	Y int
}

// Add your RPC definitions here.
type TaskArgs struct {
}

type TaskReply struct {
	TaskType      int // 0 是map任务 1是reduce任务
	MapFilename   string
	ReduceTaskNum int
	TaskId        int
	NReduce       int
	HasFinished   bool
	Rejested      bool
}

type ReduceMsgArgs struct {
	ReduceId int
	TaskId   int
}
type ReduceMsgReply struct {
	Flag bool
}

type MapMsgArgs struct {
	Filename string
	TaskId   int
}

type MapMsgReply struct {
	Flag bool
}

// Cook up a unique-ish UNIX-domain socket name
// in /var/tmp, for the coordinator.
// Can't use the current directory since
// Athena AFS doesn't support UNIX-domain sockets.
func coordinatorSock() string {
	s := "/var/tmp/5840-mr-"
	s += strconv.Itoa(os.Getuid())
	return s
}

worker.go

package mr

import (
	"encoding/json"
	"errors"
	"fmt"
	"hash/fnv"
	"io/ioutil"
	"log"
	"net/rpc"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

// for sorting by key.
type ByKey []KeyValue

// for sorting by key.
func (a ByKey) Len() int           { return len(a) }
func (a ByKey) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return a[i].Key < a[j].Key }

//
// Map functions return a slice of KeyValue.
//
type KeyValue struct {
	Key   string
	Value string
}

//
// use ihash(key) % NReduce to choose the reduce
// task number for each KeyValue emitted by Map.
// 通过该方法将一个 key val 映射到相应的reduce服务
func ihash(key string) int {
	h := fnv.New32a()
	h.Write([]byte(key))
	return int(h.Sum32() & 0x7fffffff)
}

// 将mapper和reducer抽象成两个结构体
// type Handler interface {
// 	Handle()
// }

// type MapHandler struct {

// }

// func (m *MapHandler) Handle() {

// }

// type ReduceHandler struct {

// }

// func (r *ReduceHandler) Handle() {

// }

func ReduceHandle(reduceTaskNum int, taskId int, reducef func(string, []string) string) {
	// Reduce任务
	// 读取所有的以reduceTaskNum 结尾的中间文件
	files, err := ioutil.ReadDir(".")
	if err != nil {
		log.Fatalf("cannot open cur file")
	}
	re := regexp.MustCompile(`^mr.*\d+$`)
	// 用切片记录所有的kv
	// kva := make([]KeyValue, 0) 不能全放到一个集合里
	// 应该设计数据结构如下
	// 每个文件对应一个集合
	kva := make(map[string][]KeyValue)
	ids := make(map[string]int) // 指针集合,用来遍历所有的kv集合
	for _, f := range files {
		fileName := f.Name()
		if !re.MatchString(fileName) {
			continue
		}
		// 匹配的
		parts := strings.Split(fileName, "-")
		reduceId, _ := strconv.Atoi(parts[len(parts)-1])
		if reduceId != reduceTaskNum {
			continue
		}
		file, err := os.Open(fileName)
		if err != nil {
			fmt.Println("can not open file %v   here", fileName)
		}

		dec := json.NewDecoder(file)
		for {
			var kv KeyValue
			if err := dec.Decode(&kv); err != nil {
				break
			}

			// 如果这个filename对应的切片没有创建这里需要进行创建
			if kva[fileName] == nil {
				kva[fileName] = make([]KeyValue, 0)
			}
			kva[fileName] = append(kva[fileName], kv)

		}
		ids[fileName] = 0 // 遍历从0开始
	}

	// kva全部找出来了
	// 第一步先归并  每个filename对应的集合,需要一个指针,来标记当前记录到哪里了

	fileSize := len(kva)
	successFile := 0
	hasSuccess := make(map[string]bool)
	kvs := make([]KeyValue, 0) // 最终大集合
	// 需要归并,将key一样的放到一块
	for successFile < fileSize {
		// 需要变量标记是否是字典序最小的
		miniStr := "zzzzzzzzzzzzzzzz"
		miniFile := ""
		for k, v := range kva {
			if hasSuccess[k] {
				continue
			}
			// 遍历所有的集合
			index := ids[k]
			// 判断这个index是否超出了范围
			n := len(v)
			if index == n {
				// 说明这个文件的数据完成了
				successFile++ // 只在第一次触发
				hasSuccess[k] = true
				continue
			}

			// v 是这个file的所有KeyValue Key是字符串 Value是1
			tmpStr := v[index].Key
			if tmpStr < miniStr {
				// 说明更小
				miniStr = tmpStr
				miniFile = k
			}
		}

		if successFile == fileSize {
			break
		}
		// 找到了最小的键值对
		kv := kva[miniFile][ids[miniFile]]
		ids[miniFile]++
		kvs = append(kvs, kv)
	}
	// 下一步进行Reduce  key一样的value全部放到一个地方然后传给Reduce函数

	i := 0
	output := make([]KeyValue, 0)
	// 维持顺序的遍历 不可以使用for range
	for i < len(kvs) {
		j := i + 1
		for j < len(kvs) && kvs[j].Key == kvs[i].Key {
			j++
		}
		// 上面是找到相同key的区域[i,j)

		values := []string{} // 存储所有的value,应当都为1
		for k := i; k < j; k++ {
			values = append(values, kvs[k].Value)
		}
		// 返回值是string类型表示这个词出现的总次数
		occurrenceCount := reducef(kvs[i].Key, values)
		// kvs[i].Key output
		output = append(output, KeyValue{kvs[i].Key, occurrenceCount})
		// 向文件里写这个词出现的次数和key
		i = j
	}

	// 向新文件里面写数据
	fn := "mr-out-" + strconv.Itoa(reduceTaskNum) // 写入的文件名字
	writeReduceOutputFromInner(fn, output, reduceTaskNum, taskId)

}
func MapHandle(mapFilename string, taskId int, nReduce int, mapf func(string, string) []KeyValue) {
	// 存的时候按Json存
	file, err := os.Open(mapFilename)
	if err != nil {
		log.Fatalf("cannot open %v", mapFilename)
	}
	content, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatalf("cannot open %v", mapFilename)
	}
	// map任务
	innerFiles := mapf(mapFilename, string(content))
	reduceKVMap := make(map[int][]KeyValue)
	reduceIds := make([]int, 0)

	// 最后应该是一个reduceId一个文件
	for _, v := range innerFiles {
		reduceId := ihash(v.Key) % nReduce
		if len(reduceKVMap[reduceId]) == 0 {
			// 说明没有
			reduceIds = append(reduceIds, reduceId)
		}
		reduceKVMap[reduceId] = append(reduceKVMap[reduceId], v)
	}

	// 写,遍历所有的reduceId来写

	for i := 0; i < len(reduceIds); i++ {
		reduceId := reduceIds[i]
		kvs := reduceKVMap[reduceId] // 所有某个reduceId的键值对,需要对key进行排序
		// shuffle阶段排序
		sort.Sort(ByKey(kvs))
		// 每写一个文件,就会向协调器传消息,所以会卡住
		// 写多个中间文件,但只确认一次
		// 一起写
		// err = WriteInnerToFile(fn, kvs, mapFilename, taskId)
		if err != nil {
			fmt.Println("Error creating temporary file:", err)
		}
	}

	err = WriteInnerToFile(reduceIds, reduceKVMap, mapFilename, taskId)
	if err != nil {
		fmt.Println("Error creating temporary file:", err)
	}
}

//
// main/mrworker.go calls this function.
//
func Worker(mapf func(string, string) []KeyValue,
	reducef func(string, []string) string) {
	// 这里应该是一个轮询逻辑

	// Your worker implementation here.

	for {
		taskType, mapFilename, reduceTaskNum, taskId, nReduce, err := CallForTasks()
		if err != nil {
			// 说明有问题,有问题sleep一会再continue
			time.Sleep(1 * time.Second)
			continue
		}

		if taskType == 1 {
			ReduceHandle(reduceTaskNum, taskId, reducef)
		} else {
			MapHandle(mapFilename, taskId, nReduce, mapf)
		}
		// 请求后sleep一段时间
		time.Sleep(1 * time.Second)
	}

	// uncomment to send the Example RPC to the coordinator.
	// CallExample()

}

// 确认的时候会出现问题,因为coordinator做验证的时候,保护了一种情况,就是当一个task已经超时了,超时以后请求confirm的时候会通过HashSet进行验证是否超时
// 使用的是reduceId进行验证,所以会存在超时一次就一直超时的问题
// 解决方案 继续使用维护的taskId来维护超时的问题
func writeReduceOutputFromInner(filename string, kvs []KeyValue, reduceId int, taskId int) error {
	// 向这个文件里面写入相应的数据
	tempFile, err := ioutil.TempFile("", "reduce-out-*") // 建立临时文件
	tempName := tempFile.Name()
	defer os.Remove(tempFile.Name()) // 在程序结束前删除临时文件
	if err != nil {
		fmt.Println("Error creating temporary file:", err)
		return err
	}
	i := 0
	for i < len(kvs) {
		// 向临时文件里面写内容
		fmt.Fprintf(tempFile, "%v %v\n", kvs[i].Key, kvs[i].Value)
		i++
	}
	tempFile.Close()

	// 然后是改名阶段,改之前需要先请求一手确认有木有毛病,需要
	flag := CallForReduceConfirm(reduceId, taskId) // true说明超时或者报错

	if flag {
		fmt.Println("请求超时")
		// 让他超时
		err := errors.New("写入文件失败")
		return err
	}

	// 改名
	if err := os.Rename(tempName, filename); err != nil {
		// 这里进行回滚
		return err
	}

	return nil
}

// 将map的产物写进文件
func WriteInnerToFile(reduceIds []int, reduceKVMap map[int][]KeyValue, mapFilename string, taskId int) error {
	tmpNameMap := make(map[string]string) // 需要一个临时文件名字和真实文件名之间的一个映射关系表

	for i := 0; i < len(reduceIds); i++ {
		reduceId := reduceIds[i]
		kvs := reduceKVMap[reduceId] // 所有某个reduceId的键值对,需要对key进行排序
		fn := "mr-" + strconv.Itoa(taskId) + "-" + strconv.Itoa(reduceId)
		tempFile, err := ioutil.TempFile("", "map-inner-*") // 建立临时文件
		tmpNameMap[tempFile.Name()] = fn
		if err != nil {
			fmt.Println("Error creating temporary file:", err)
			return err
		}
		defer os.Remove(tempFile.Name()) // 在程序结束前删除临时文件
		enc := json.NewEncoder(tempFile)
		// 写的时候注意,使用JSON的数据格式

		for i := 0; i < len(kvs); i++ {
			// 这里有问题写文件不是按顺序写的
			enc.Encode(&reduceKVMap[reduceId][i])
		}
		// 关闭临时文件
		err = tempFile.Close()
		if err != nil {
			fmt.Println("Error closing temporary file:", err)
			return err
		}

	}
	// 上面写所有的临时文件
	// fn := "mr-" + strconv.Itoa(taskId) + "-" + strconv.Itoa(reduceId)
	// 然后我们请求协调器,看看有没有问题

	flag := CallForMapConfirm(mapFilename, taskId) // 确认的时候有问题,应该一个文件请求一次,请求完没问题,全部改名字
	if !flag {
		// 可以改名
		for k, v := range tmpNameMap {
			if err := os.Rename(k, v); err != nil {
				// 这里进行回滚
				CallBack(tmpNameMap)
				return err
			}
		}
	}

	return nil
}
func fileExists(filename string) bool {
	_, err := os.Stat(filename)
	return !os.IsNotExist(err)
}
func CallBack(tmpNameMap map[string]string) error {
	// 把所有Value的路径的全部删了
	for _, v := range tmpNameMap {
		if fileExists(v) {
			// 存在
			err := os.Remove(v)
			return err
		}
	}
	return nil
}

func CallForTasks() (taskType int, mapFilename string, reduceTaskNum int, taskId int, nReduce int, err error) {
	args := &TaskArgs{}
	replys := &TaskReply{}
	ok := call("Coordinator.AssignTask", args, replys)
	if !ok {
		err = errors.New("调用失败")
		return
	}
	if replys.Rejested {
		err = errors.New("请求被拒绝")
		return
	}
	if replys.HasFinished {
		err = errors.New("任务已经完成")
		return
	}
	mapFilename = replys.MapFilename
	reduceTaskNum = replys.ReduceTaskNum
	taskId = replys.TaskId
	taskType = replys.TaskType
	nReduce = replys.NReduce

	// taskType, mapFilename, _, taskId, nReduce
	return taskType, mapFilename, reduceTaskNum, taskId, nReduce, nil
}

// 调用Reduce任务的确认回执
func CallForReduceConfirm(reduceId int, taskId int) bool {
	args := &ReduceMsgArgs{}
	args.ReduceId = reduceId
	args.TaskId = taskId
	replys := &ReduceMsgReply{}
	ok := call("Coordinator.ReceiveReduceMsg", args, replys)
	if !ok {
		fmt.Printf("call failed!\n")
		return true // true表示报错
	}

	return replys.Flag
}

// 调用Map任务的确认回执
func CallForMapConfirm(mapFilename string, taskId int) bool {
	args := &MapMsgArgs{}
	args.TaskId = taskId
	args.Filename = mapFilename
	replys := &MapMsgReply{}
	ok := call("Coordinator.ReceiveMapMsg", args, replys)
	if !ok {
		fmt.Printf("call failed!\n")
		return true
	}
	return replys.Flag

}

//
// send an RPC request to the coordinator, wait for the response.
// usually returns true.
// returns false if something goes wrong.
//
func call(rpcname string, args interface{}, reply interface{}) bool {
	// c, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")
	sockname := coordinatorSock()
	c, err := rpc.DialHTTP("unix", sockname)
	if err != nil {
		log.Fatal("dialing:", err)
	}
	defer c.Close()

	err = c.Call(rpcname, args, reply)
	if err == nil {
		return true
	}

	fmt.Println(err)
	return false
}

coordinator.go

package mr

import (
	"log"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"strconv"
	"sync"
	"time"
)

// 需要实现 1 判断是否包含 2 remove
type HashSet struct {
	tm   map[int]bool
	lock sync.Mutex
}

func (o *HashSet) put(taskId int) {
	o.lock.Lock()
	defer o.lock.Unlock()
	o.tm[taskId] = true
}

func (o *HashSet) contains(taskId int) bool {
	o.lock.Lock()
	defer o.lock.Unlock()
	_, exists := o.tm[taskId]
	return exists
}

func (o *HashSet) remove(taskId int) {
	o.lock.Lock()
	defer o.lock.Unlock()
	delete(o.tm, taskId)
}

type Counter struct {
	num  int
	lock sync.Mutex
}

func (t *Counter) getCounter() int {
	t.lock.Lock()
	defer t.lock.Unlock()
	t.num++
	return t.num
}

type SafeQueue struct {
	elements []string
	lock     sync.Mutex
}

type SafeStringChanMap struct {
	m    map[string]chan bool
	lock sync.Mutex
}

func (ssc *SafeStringChanMap) set(s string, b bool) {
	ssc.lock.Lock()         // 上锁
	defer ssc.lock.Unlock() // 关锁

	// 往里面塞东西捏
	ssc.m[s] <- b
}

func (ssc *SafeStringChanMap) get(s string) chan bool {
	ssc.lock.Lock() // 上锁
	if ssc.m[s] == nil {
		// 需要创建chan
		ssc.m[s] = make(chan bool)
	}
	a := ssc.m[s]
	defer ssc.lock.Unlock() // 关锁
	return a
}

type SafeIntChanMap struct {
	m    map[int]chan bool
	lock sync.Mutex
}

func (ssc *SafeIntChanMap) set(s int, b bool) {
	ssc.lock.Lock()         // 上锁
	defer ssc.lock.Unlock() // 关锁

	// 往里面塞东西捏
	ssc.m[s] <- b
}

func (ssc *SafeIntChanMap) get(s int) chan bool {
	ssc.lock.Lock() // 上锁
	if ssc.m[s] == nil {
		// 需要创建chan
		ssc.m[s] = make(chan bool)
	}
	a := ssc.m[s]
	defer ssc.lock.Unlock() // 关锁
	return a
}

type Coordinator struct {
	// Reduce的问题是nReduce 需要n个机器处理Reduce任务
	taskCounter Counter

	isExit             bool
	mapSuccessCount    int
	reduceSuccessCount int
	fileCount          int
	reduceCount        int
	// Your definitions here.
	// 需要有参数记录当前有多少个map任务有多少reduce任务
	mapTaskQueue    SafeQueue
	reduceTaskQueue SafeQueue
	// 每个string对应一个chan 每个string不会重复访问
	mapTaskMsg    SafeStringChanMap
	reduceTaskMsg SafeIntChanMap

	// 超时集合,负责控制超时的任务
	mapOutTimeSet    HashSet
	reduceOutTimeSet HashSet
}

// Enqueue 在队列末尾添加元素
func (q *SafeQueue) Enqueue(element string) {
	q.lock.Lock()
	defer q.lock.Unlock()
	q.elements = append(q.elements, element)
}

// Dequeue 从队列头部删除元素并返回
func (q *SafeQueue) Dequeue() (string, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()
	if len(q.elements) == 0 {
		return "", false
	}
	element := q.elements[0]
	q.elements = q.elements[1:]
	return element, true
}

// IsEmpty 检查队列是否为空
func (q *SafeQueue) IsEmpty() bool {
	q.lock.Lock()
	defer q.lock.Unlock()
	return len(q.elements) == 0
}

// Your code here -- RPC handlers for the worker to call.

//
// an example RPC handler.
//
// the RPC argument and reply types are defined in rpc.go.
//
func (c *Coordinator) Example(args *ExampleArgs, reply *ExampleReply) error {
	reply.Y = args.X + 1
	return nil
}

// 返回是否超时
func (c *Coordinator) ReceiveReduceMsg(args *ReduceMsgArgs, reply *ReduceMsgReply) error {
	// 问题是比如这个文件12s以后回调这个方法,那就应该抛弃这个消息
	// 首先需要确认这个taskId是否存在超时的集合当中

	reduceId := args.ReduceId
	taskId := args.TaskId
	// 判断是否超时
	flag := c.reduceOutTimeSet.contains(taskId)
	if flag {
		// 说明超时了
		// fmt.Println("超时了")
		reply.Flag = true
		return nil
	}

	// 说明执行成功
	c.reduceTaskMsg.set(reduceId, true)
	// fmt.Println(reduceId)
	reply.Flag = false // 成功

	return nil
}

// 返回是否超时
func (c *Coordinator) ReceiveMapMsg(args *MapMsgArgs, reply *MapMsgReply) error {
	// 问题是比如这个文件12s以后回调这个方法,那就应该抛弃这个消息
	// 首先需要确认这个taskId是否存在超时的集合当中
	taskId := args.TaskId
	filename := args.Filename
	// 判断是否超时
	flag := c.mapOutTimeSet.contains(taskId)
	if flag {
		// 说明超时了
		reply.Flag = true
		return nil
	}
	// 说明执行成功
	c.mapTaskMsg.set(filename, true)
	reply.Flag = false // 成功

	return nil
}

// 分配任务以后使用管道,等待信息,再另外一个方法当中
func (c *Coordinator) AssignTask(args *TaskArgs, reply *TaskReply) error {

	// 分配任务的时候生成一个taskId
	taskId := c.taskCounter.getCounter()
	if c.mapSuccessCount == c.fileCount {
		// 说明map完成,分配reduce任务
		// 判断一下如果reduce任务也全部Success,就可以结束这个任务
		if c.reduceSuccessCount == c.reduceCount {
			reply.HasFinished = true
			c.isExit = true // 结束任务 这时候得告诉他得返回true
			// 然后要通知一下回去,告诉worker任务结束
			return nil // 返回
		}
		// 分配Reduce任务
		if !c.reduceTaskQueue.IsEmpty() {
			// 分配任务
			reduceId, _ := c.reduceTaskQueue.Dequeue()
			rid, _ := strconv.Atoi(reduceId)
			reply.ReduceTaskNum = rid
			reply.TaskType = 1
			reply.TaskId = taskId
			// 要做的是,读取中间文件生成out文件,只读取reduceId的文件,需要超时控制
			go func() {
				// 这个goroutine 不处理完,别的线程不可能可以处理这个filename
				// fmt.Println(rid)
				select {
				case value := <-c.reduceTaskMsg.get(rid):
					// fmt.Println(value)
					// 两种可能 成功或者失败
					if value {
						// 成功
						c.reduceSuccessCount++
					} else {
						c.reduceTaskQueue.Enqueue(reduceId) // 放回去
					}
				case <-time.After(10 * time.Second):
					// fmt.Printf("超时了 " + strconv.Itoa(rid))
					//重新放回去
					c.reduceTaskQueue.Enqueue(reduceId)
					// 将超时的ID存到集合里
					c.reduceOutTimeSet.put(taskId)
				}
			}()
		} else {
			// 没任务的时候要告诉回去
			reply.Rejested = true
		}
	} else {
		// 分配map任务
		// 优先分配map任务
		if !c.mapTaskQueue.IsEmpty() {
			// 分配mapFiles 任务 如果有空
			fileName, _ := c.mapTaskQueue.Dequeue()

			reply.MapFilename = fileName
			reply.TaskType = 0
			reply.TaskId = taskId
			reply.NReduce = c.reduceCount
			go func() {
				// 这个goroutine 不处理完,别的线程不可能可以处理这个filename
				select {
				case value := <-c.mapTaskMsg.get(fileName):
					// 两种可能 成功或者失败
					if value {
						// 成功
						c.mapSuccessCount++
					} else {
						c.mapTaskQueue.Enqueue(fileName) // 放回去
					}
				case <-time.After(10 * time.Second):
					// fmt.Println("超时了")
					//重新放回去
					c.mapTaskQueue.Enqueue(fileName)
					// 将超时的ID存到集合里
					c.mapOutTimeSet.put(taskId)
				}
			}()
		} else {
			// 没任务的时候要告诉回去
			reply.Rejested = true
		}
	}
	return nil
}

//
// start a thread that listens for RPCs from worker.go
//
func (c *Coordinator) server() {
	rpc.Register(c)
	rpc.HandleHTTP()
	//l, e := net.Listen("tcp", ":1234")
	sockname := coordinatorSock()
	// fmt.Println(sockname)
	os.Remove(sockname)
	l, e := net.Listen("unix", sockname)
	if e != nil {
		log.Fatal("listen error:", e)
	}
	go http.Serve(l, nil)
}

//
// main/mrcoordinator.go calls Done() periodically to find out
// if the entire job has finished.
//
func (c *Coordinator) Done() bool {
	// Your code here.

	return c.isExit
}

//
// create a Coordinator.
// main/mrcoordinator.go calls this function.
// nReduce is the number of reduce tasks to use.
//
func MakeCoordinator(files []string, nReduce int) *Coordinator {
	c := &Coordinator{
		taskCounter:        Counter{},
		isExit:             false,
		mapOutTimeSet:      HashSet{tm: make(map[int]bool)},
		reduceOutTimeSet:   HashSet{tm: make(map[int]bool)},
		mapSuccessCount:    0,
		reduceSuccessCount: 0,
		fileCount:          0,
		reduceCount:        0,
		mapTaskQueue:       SafeQueue{elements: make([]string, 0)},
		reduceTaskQueue:    SafeQueue{elements: make([]string, 0)},
		mapTaskMsg:         SafeStringChanMap{m: make(map[string]chan bool)},
		reduceTaskMsg:      SafeIntChanMap{m: make(map[int]chan bool)},
	}
	// Your code here.
	// 通过files赋值给全局的变量,由变量作为分配任务的依据
	for _, v := range files {
		c.mapTaskQueue.Enqueue(v)
		c.fileCount++
	}
	c.reduceCount = nReduce
	for i := 0; i < nReduce; i++ {
		c.reduceTaskQueue.Enqueue(strconv.Itoa(i))
	}

	c.server()

	// 决定分配worker,然后安排n个Reduce处理情况
	return c
}

如果有疑问可以互相交流,回复很快捏

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值