Julia集图像渲染被并发破坏了。

huangapple go评论82阅读模式
英文:

Julia set image rendering ruined by concurrency

问题

这段代码的变化导致图像看起来非常不同。图案基本相同,但是出现了许多之前不存在的白色像素。

这是因为你在Julia()函数中使用了并发操作。通过使用go关键字创建了一个匿名函数作为goroutine,在每个像素点上都启动了一个goroutine来计算对应的n值并设置像素颜色。

然而,这种并发操作可能导致竞争条件。由于goroutine的执行是异步的,它们可能会同时访问和修改共享的img对象。这可能导致图像数据的不一致性,从而产生了额外的白色像素。

为了解决这个问题,你可以使用互斥锁(mutex)来保护对img对象的并发访问。在每个goroutine中,使用互斥锁来确保每次只有一个goroutine能够修改img对象。这样可以避免竞争条件,保证图像数据的一致性。

另外,你还需要在主函数中等待所有的goroutine执行完毕,可以使用sync.WaitGroup来实现。在每个goroutine启动之前,调用wg.Add(1)增加等待组的计数器,然后在goroutine结束时调用wg.Done()减少计数器。最后,使用wg.Wait()等待所有的goroutine执行完毕。

下面是修改后的代码示例:

import (
	"sync"
)

func Julia(f ComplexFunc, n int) image.Image {
	bounds := image.Rect(-n/2, -n/2, n/2, n/2)
	img := image.NewRGBA(bounds)
	s := float64(n / 4)
	var wg sync.WaitGroup
	var mutex sync.Mutex

	for i := bounds.Min.X; i < bounds.Max.X; i++ {
		for j := bounds.Min.Y; j < bounds.Max.Y; j++ {
			wg.Add(1)
			go func(i, j int) {
				defer wg.Done()
				n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
				r := uint8(0)
				g := uint8(0)
				b := uint8(n % 32 * 8)
				mutex.Lock()
				img.Set(i, j, color.RGBA{r, g, b, 255})
				mutex.Unlock()
			}(i, j)
		}
	}

	wg.Wait()
	return img
}

通过添加互斥锁和等待组,你可以确保并发操作的安全性,并消除额外的白色像素。

英文:

I have the following code that I am to change into a concurrent program.

// Stefan Nilsson 2013-02-27

// This program creates pictures of Julia sets (en.wikipedia.org/wiki/Julia_set).
package main

import (
	&quot;image&quot;
	&quot;image/color&quot;
	&quot;image/png&quot;
	&quot;log&quot;
	&quot;math/cmplx&quot;
	&quot;os&quot;
	&quot;strconv&quot;
)

type ComplexFunc func(complex128) complex128

var Funcs []ComplexFunc = []ComplexFunc{
	func(z complex128) complex128 { return z*z - 0.61803398875 },
	func(z complex128) complex128 { return z*z + complex(0, 1) },
}

func main() {
	for n, fn := range Funcs {
		err := CreatePng(&quot;picture-&quot;+strconv.Itoa(n)+&quot;.png&quot;, fn, 1024)
		if err != nil {
			log.Fatal(err)
		}
	}
}

// CreatePng creates a PNG picture file with a Julia image of size n x n.
func CreatePng(filename string, f ComplexFunc, n int) (err error) {
	file, err := os.Create(filename)
	if err != nil {
		return
	}
	defer file.Close()
	err = png.Encode(file, Julia(f, n))
	return
}

// Julia returns an image of size n x n of the Julia set for f.
func Julia(f ComplexFunc, n int) image.Image {
	bounds := image.Rect(-n/2, -n/2, n/2, n/2)
	img := image.NewRGBA(bounds)
	s := float64(n / 4)
	for i := bounds.Min.X; i &lt; bounds.Max.X; i++ {
		for j := bounds.Min.Y; j &lt; bounds.Max.Y; j++ {
			n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
			r := uint8(0)
			g := uint8(0)
			b := uint8(n % 32 * 8)
			img.Set(i, j, color.RGBA{r, g, b, 255})
		}
	}
	return img
}

// Iterate sets z_0 = z, and repeatedly computes z_n = f(z_{n-1}), n &#226;‰&#165; 1,
// until |z_n| &gt; 2  or n = max and returns this n.
func Iterate(f ComplexFunc, z complex128, max int) (n int) {
	for ; n &lt; max; n++ {
		if real(z)*real(z)+imag(z)*imag(z) &gt; 4 {
			break
		}
		z = f(z)
	}
	return
}

I have decided to try and make the Julia() function concurrent. So I changed it to:

func Julia(f ComplexFunc, n int) image.Image {	
	bounds := image.Rect(-n/2, -n/2, n/2, n/2) 	
	img := image.NewRGBA(bounds)				
	s := float64(n / 4)							 
	for i := bounds.Min.X; i &lt; bounds.Max.X; i++ {
		for j := bounds.Min.Y; j &lt; bounds.Max.Y; j++ {
			go func(){
				n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
				r := uint8(0)	
				g := uint8(0)	
				b := uint8(n % 32 * 8)	
				img.Set(i, j, color.RGBA{r, g, b, 255}) 
			}()
		}
	}
	return img 

This change causes the images to look very different. The patterns are essentially the same, but there are a lot of white pixels that were not there before.

What is happening here?

答案1

得分: 0

有两个问题:

  1. 你实际上没有等待 goroutine 完成。
  2. 你没有将 ij 传递给 goroutine,所以它们几乎总是最后的 ij

你的函数应该类似于:

func Julia(f ComplexFunc, n int) image.Image {
    var wg sync.WaitGroup
    bounds := image.Rect(-n/2, -n/2, n/2, n/2)
    img := image.NewRGBA(bounds)
    s := float64(n / 4)
    for i := bounds.Min.X; i < bounds.Max.X; i++ {
        for j := bounds.Min.Y; j < bounds.Max.Y; j++ {
            wg.Add(1)
            go func(i, j int) {
                n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
                r := uint8(0)
                g := uint8(0)
                b := uint8(n % 32 * 8)
                img.Set(i, j, color.RGBA{r, g, b, 255})
                wg.Done()
            }(i, j)
        }
    }
    wg.Wait()
    return img
}

一个额外的提示,在并发编程中,通常使用 race detector 来测试你的代码是一个好主意。

可能需要使用互斥锁来调用 img.Set,但我不太确定,而且我现在无法测试。

英文:

There are 2 problems:

  1. You don't actually wait for your goroutines to finish.
  2. You don't pass i and j to the goroutine, so they will almost always be the last i and j.

Your function should look something like:

func Julia(f ComplexFunc, n int) image.Image {
	var wg sync.WaitGroup
	bounds := image.Rect(-n/2, -n/2, n/2, n/2)
	img := image.NewRGBA(bounds)
	s := float64(n / 4)
	for i := bounds.Min.X; i &lt; bounds.Max.X; i++ {
		for j := bounds.Min.Y; j &lt; bounds.Max.Y; j++ {
			wg.Add(1)
			go func(i, j int) {
				n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
				r := uint8(0)
				g := uint8(0)
				b := uint8(n % 32 * 8)
				img.Set(i, j, color.RGBA{r, g, b, 255})
				wg.Done()
			}(i, j)
		}
	}
	wg.Wait()
	return img
}

A bonus tip, when diving into concurrency, it's usually a good idea to try your code with the race detector.

You might have to use a mutex to call img.Set but I'm not very sure and I can't test atm.

huangapple
  • 本文由 发表于 2016年4月11日 22:11:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/36551154.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定