多线程 Mandelbrot 集代码输出不正确的图像

Multithreaded Mandelbrot set code outputs incorrect image

#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <complex>
#include <fstream>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::seconds;
using std::complex;
using std::cout;
using std::endl;
using std::this_thread::sleep_for;
using std::ofstream;
using std::thread;
using std::mutex;
using std::condition_variable;
using std::unique_lock;
mutex mutex1;
mutex mutex2;
std::condition_variable done;

typedef std::chrono::steady_clock the_clock;



const int WIDTH1 = 960;
const int HEIGHT1 = 600;
const int WIDTH2 = 1920;
const int HEIGHT2 = 1200;
int finished_threads = 0;


const int MAX_ITERATIONS = 500;


uint32_t image[HEIGHT2][WIDTH2];



struct ThreadArgs { int id; int delay; };
void myThreadFunc(ThreadArgs args)

{
    for (int i = 0; i < 1; i++) {
        sleep_for(seconds(args.delay));
        cout << args.id;
    }
}
void write_tga(const char *filename)
{
    unique_lock<mutex> lock(mutex2);
    while (finished_threads < 2) {
        done.wait(lock);
    }
    ofstream outfile(filename, ofstream::binary);

    uint8_t header[18] = {
        0, // no image ID
        0, // no colour map
        2, // uncompressed 24-bit image
        0, 0, 0, 0, 0, // empty colour map specification
        0, 0, // X origin
        0, 0, // Y origin
        WIDTH2 & 0xFF, (WIDTH2 >> 8) & 0xFF, // width
        HEIGHT2 & 0xFF, (HEIGHT2 >> 8) & 0xFF, // height
        24, // bits per pixel
        0, // image descriptor
    };
    outfile.write((const char *)header, 18);

    for (int y = 0; y < HEIGHT2; ++y)
    {
        for (int x = 0; x < WIDTH2; ++x)
        {
            uint8_t pixel[3] = {
                image[y][x] & 0xFF, // blue channel
                (image[y][x] >> 8) & 0xFF, // green channel
                (image[y][x] >> 16) & 0xFF, // red channel
            };
            outfile.write((const char *)pixel, 3);
        }
    }

    outfile.close();
    if (!outfile)
    {

        cout << "Error writing to " << filename << endl;
        exit(1);
    }
}


// Render the Mandelbrot set into the image array.
// The parameters specify the region on the complex plane to plot.
void compute_mandelbrot(double left, double right, double top, double bottom)

{
    unique_lock<mutex> lock(mutex2);

    for (int y = 0; y < HEIGHT1; ++y)
    {
        for (int x = 0; x < WIDTH1; ++x)
        {

            complex<double> c(left + (x * (right - left) / WIDTH2),
                top + (y * (bottom - top) / HEIGHT2));

            // Start off z at (0, 0).
            complex<double> z(0.0, 0.0);

            // Iterate z = z^2 + c until z moves more than 2 units
            // away from (0, 0), or we've iterated too many times.
            int iterations = 0;
            mutex1.lock();
            while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
            {
                z = (z * z) + c;

                ++iterations;
            }
            mutex1.unlock();
            if (iterations == MAX_ITERATIONS)
            {

                image[y][x] = 0x000000; // black
            }
            else
            {

                image[y][x] = 0xFFFFFF; // white
                finished_threads = finished_threads + 1;
                done.notify_all();
            }
        }
    }
}
void compute_mandelbrot2(double left2, double right2, double top2, double bottom2)

{
    unique_lock<mutex> lock(mutex2);
    //map <int, int> val = map<int, int>(0, MAX_ITERATIONS);
    //map <int, int> colourval = map<int, int>(0, MAX_ITERATIONS);
    for (int y = HEIGHT1; y < HEIGHT2; ++y)
    {
        for (int x = HEIGHT1; x < WIDTH2; ++x)
        {

            complex<double> c(left2 + (x * (right2 - left2) / WIDTH2),
                top2 + (y * (bottom2 - top2) / HEIGHT2));

            // Start off z at (0, 0).
            complex<double> z(0.0, 0.0);

            // Iterate z = z^2 + c until z moves more than 2 units

            int iterations = 0;
            mutex1.lock();
            while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
            {
                z = (z * z) + c;

                ++iterations;
            }
            mutex1.unlock();
            if (iterations == MAX_ITERATIONS)
            {
                // z didn't escape from the circle.
                // This point is in the Mandelbrot set.
                image[y][x] = 0x000000; // black
            }
            else
            {
                // z escaped within less than MAX_ITERATIONS
                // iterations. This point isn't in the set.
                image[y][x] = 0xFFFFFF; // white
                finished_threads = finished_threads + 1;
                done.notify_one();
            }
        }
    }
}


int main(int argc, char *argv[])
{

    std::thread myThread;
    std::thread myThread2;
    std::thread myThread3;

    ThreadArgs args;

    myThread = std::thread(compute_mandelbrot, -2.0, 1.0, 1.125, -1.125);
    myThread3 = std::thread(compute_mandelbrot2, -2.0, 1.0, 1.125, -1.125);
    myThread2 = std::thread(write_tga, "output.tga");

    cout << "Please wait..." << endl;

    // Start timing
    the_clock::time_point start = the_clock::now();



    myThread.join();
    myThread3.join();




    // Stop timing
    the_clock::time_point end = the_clock::now();

    // Compute the difference between the two times in milliseconds
    auto time_taken = duration_cast<milliseconds>(end - start).count();
    cout << "Computing the Mandelbrot set took " << time_taken << " ms." << endl;


    myThread2.join();

    return 0;
}

上面是多线程版本的代码,它输出了一个不正确的集合版本,其中大部分是黑色的,但有些是正确的,所以我不知道问题是什么:Mandlebrot threaded but the non-threaded version outputs a correct Mandelbrot setMandelbrot not threaded问题可能与我使用多线程的方式有关:我只是不知道我做错了什么。感谢任何帮助。

OK,'simple'事情先!您在内部 (x) 循环的控制语句的 compute_mandelbrot2 函数中有一个 'typo';这一行:

    for (int x = HEIGHT1; x < WIDTH2; ++x)

应该(当然)是:

    for (int x = WIDTH1; x < WIDTH2; ++x) // WIDTH1 not HEIGHT1

现在来了解更多 'subtle' 内容。您正试图通过将 both 'x' 和 'y' 范围分成两半来将计算分成两半。这是行不通的,因为这需要 四个 个线程,每个线程处理相关的 quarter 情节。为了保持只使用两半,两个线程函数 中的 'y' 范围必须 覆盖整个图(但 'x' 范围可以适当地分成两部分).

因此,您的外部 ('y') 循环控制语句应涵盖 整个 范围 both 线程函数,它们都应该是这样的:

for (int y = 0; y < HEIGHT2; ++y) {
    //...

我已经用上述三个更改测试了您的代码,它生成了正确的 Mandelbrot 集图像。随时要求进一步澄清 and/or 解释。