golang goroutine传参引发的问题

最近在学习golang,记一次goroutine传参引发的计算结果和预期不一致问题。

例子是一个多goroutine并行计算一个数的值,一个task的chan,和一个result的chan用来存储分配的每个任务,和每个任务的计算结果,最后将所有的任务结果相加
先看有问题的代码:

package main

import (
	"fmt"
	"sync"
)

//利用chan将计算任务拆分成多个task,并发计算

type task struct {
    
    
	begin  int
	end    int
	result chan int
}

func (t *task) do() {
    
    
	sum := 0
	for i := t.begin; i <= t.end; i++ {
    
    
		sum = sum + i
	}
	fmt.Printf("this sum is %d\n", sum)
	t.result <- sum
}

//分配task,将总的任务分割
func InitTask(taskchan chan task, r chan int, count int) {
    
    
	//10个任务,每个任务的数目
	cp := count / 10
	//余数
	du := count % 10
	high := du + cp*10
	for j := 0; j < 10; j++ {
    
    
		var b int
		var e int
		if j == 9 && du != 0 {
    
    
			b = cp*j + 1
			e = high

		} else {
    
    

			b = cp*j + 1
			e = cp * (j + 1)
		}
		fmt.Printf("b is %d, e is %d\n", b, e)
		tak := task{
    
    
			begin:  b,
			end:    e,
			result: r,
		}
		taskchan <- tak
	}
	//切记关闭chan,否则会死锁
	close(taskchan)
}

func DisbuteTask(tak chan task, result chan int, wg *sync.WaitGroup) {
    
    
	for v := range tak {
    
    
		fmt.Println("开始一个计算")
		wg.Add(1)
		//注意,这里直接使用了函数外的v变量
		go func() {
    
    
			defer wg.Done()
			v.do()
			fmt.Println("结束一个计算")
		}()
	}
	要等待每个task运行完 关闭通道,这句换个地方会导致结果出错
	wg.Wait()
	fmt.Println("关闭计算结果通道")
	//切记关闭chan,否则会死锁
	close(result)
}

func main() {
    
    
	//任务
	taskchan := make(chan task, 10)
	//结果
	resultchan := make(chan int, 10)

	var wg sync.WaitGroup
	go InitTask(taskchan, resultchan, 118)
	//计算每个task的sum
	go DisbuteTask(taskchan, resultchan, &wg)

	sum := ProcessResult(resultchan)
	println(sum)
}

func ProcessResult(result chan int) int {
    
    
	sum := 0
	for v := range result {
    
    
		sum = sum + v
	}
	return sum
}

执行结果
在这里插入图片描述
与预期结果不一致,而且每次执行的结果都不一样,这可把我整迷糊了,反复看了代码,逻辑没什么问题呀,折腾了老半天,打印了许多日志,发现了一个有意思的现象:
在这里插入图片描述
这里竟然两次计算结果是一样的,但是分割的日志显示是正确的
在这里插入图片描述
到这里都是正常的,那问题一定出在DisbuteTask()这个函数,我反复看了一下这个函数,导致两次计算结果一样那必定是两次传入do函数的task的begin和end值是一样的,什么情况下才会发生这种情况?
在观察下这个函数,这里每个task结构调用了do方法,那么只有一种解释:两次循环传入了同一个v结构体,那么肯定是当进入for循环时值是正确的,然后当开启一个goroutine的时候v的值被动态刷新了,导致了结果的出乎意料。
再来看一下这个函数:

func DisbuteTask(tak chan task, result chan int, wg *sync.WaitGroup) {
    
    
	for v := range tak {
    
    
		fmt.Println("开始一个计算")
		wg.Add(1)
		go func() {
    
    
			defer wg.Done()
			v.do()
			fmt.Println("结束一个计算")
		}()
	}
	wg.Wait()
	fmt.Println("关闭计算结果通道")
	//切记关闭chan,否则会死锁
	close(result)
}

那么,正确的代码应该是:

func DisbuteTask(tak chan task, result chan int, wg *sync.WaitGroup) {
    
    
	for v := range tak {
    
    
		fmt.Println("开始一个计算")
		wg.Add(1)
		go func(v task) {
    
    
			defer wg.Done()
			v.do()
			fmt.Println("结束一个计算")
		}(v)
	}
	wg.Wait()
	fmt.Println("关闭计算结果通道")
	//切记关闭chan,否则会死锁
	close(result)
}

在启动一个goroutine的时候将v结构体显式的传入函数,最后的结果就对了!

猜你喜欢

转载自blog.csdn.net/zb199738/article/details/124790287