Go并发调用image包生成元胞自动机图像出现异常行为问题求助
问题根因
你遇到的并发渲染异常本质是写入范围重叠导致的数据竞争,和image包的Set方法本身无关:
- 你当前的渲染循环使用了
<=作为边界判断,会导致相邻行、相邻单元格的边界像素被多个goroutine同时写入:
以pSize=2为例,第i行的y轴范围是[i*2, i*2 + 2],共3个像素值,其中i*2 +2正好是第i+1行y轴的起始值,该位置的像素会被i和i+1两个goroutine同时修改,属于典型的并发写冲突,每次运行的结果都不可预期,和你观察到的现象完全吻合。 - 单线程执行时所有写入操作是顺序执行的,即使有重叠写入,后执行的写入会覆盖前一次的结果,最终输出符合预期,所以问题只会在并发场景下暴露。
修复方案
将renderRow函数中的两个循环边界判断从<=改为<即可,保证每个像素只会被唯一的goroutine写入:
func renderRow(wg *sync.WaitGroup, img *image.RGBA, i int, pSize int) { defer wg.Done() var lpc = 0 for j := 0; j < 64; j++ { for k := range sim[i] { // 边界判断从<=改为< for l := lpc * pSize; l < (lpc*pSize)+pSize; l++ { // 边界判断从<=改为< for m := i * pSize; m < (i*pSize)+pSize; m++ { if getBit(sim[i][k], j) == 1 { img.Set(l, m, black) } else { img.Set(l, m, white) } } } lpc++ } } }
补充说明
- 关于你对
Set方法语法的疑惑:s := p.Pix[i : i+4 : i+4]是Go的全切片表达式,这里的s是Pix底层数组的引用,直接指向目标像素对应的4个字节内存,对s[0]~s[3]的赋值本质就是修改Pix数组的内容,不会因为s被GC就失效,该设计是完全安全的。 - Go标准库的
image.RGBA的Set方法本身没有内置锁,但只要不同goroutine写入的像素坐标完全不重叠就不会有并发问题:Set方法仅对预分配的Pix切片的固定偏移位置做写入,不会修改切片的长度、容量等元数据,也没有共享可变状态,重叠写入是你遇到问题的唯一原因。 - 你可以使用
go run -race命令运行程序,Go的内置竞争检测器会直接定位到重叠写入的冲突位置,验证上述结论。
内容的提问来源于stack exchange,提问作者Mo0nbase
相关产品推荐
相关产品推荐

