-1

I want to make a Mandelbrot set, and make the whole process multithreaded. I am trying to do this with the internal thread library (or how that is exactly called). I can't use openmp for some reason. I think that is because of my laptop (mac m1).

The way I am trying to do this is by making a vector with multiple threads in it, and giving each of them a task to do. The task it has to do is calculate if a pixel is diverging or not. I then want to wait for every thread to finish, and put the pixels into the ppm picture.

This is my code:

#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <complex> 
#include <fstream>   
#include <thread>

using namespace std;

double xmin = -2;
double xmax = 1;
double ymin = -1;
double ymax = 1;
float xDist = xmax - xmin;
float yDist = ymax - ymin;
float scaleFactor = xDist/yDist;
int sizeF = 1000;


int amountOfThreads = 1;

vector< bool > threadsOutcome;
vector<std::thread> threads;

void func(int xj, int yi, int p) {
    double x = xmin + xj*xDist/(sizeF*scaleFactor);
    double y = ymin + yi*yDist/sizeF;
    //cout << x << ", " << y << endl;
    int max = 1000;
    complex<long double> comp(x,y);
    complex<long double> complexNUM(0,0);
    for(int i = 0; i < max; i++) {
        complexNUM = pow(complexNUM,2) + comp;
        if (!std::isfinite(abs(complexNUM))) {
            threadsOutcome.at(p) = false;
            return;
        }
    }
    threadsOutcome.at(p) = true;
    return;
}

int main()
{
    for (int i = 0; i < amountOfThreads; i++) {
        threadsOutcome.push_back(false);
    }


    ofstream img;
    img.open("picture.ppm");

    img << "P3" << endl;
    img << sizeF*scaleFactor << " " << sizeF << endl;
    img << "255" << endl;

    
    std::complex<long double> c0(1, -1);
    vector< complex<long double> > re;
    vector< complex<long double> > im;




    if (!img.is_open())
    {
        cout << "Program aborted." << endl;
        return -1;
    } else {
        cout << "Starting!" << endl;
    }
    
    for (int i = 0; i < sizeF; i++) {
        for (int j = 0; j < sizeF*scaleFactor; j++) {
            //cout << i << ", " << j << endl;
            //cout << j << endl;
            //deciding how many pixels are left until the right border
            if (sizeF*scaleFactor - j >= amountOfThreads) {
                //doing the multiple
                for (int k = 0; k < amountOfThreads; k++) {
                    thread temp(func, j+k, i, k);
                    temp.detach();
                    threads.push_back(temp);
                    //func(j+k, i, k);
                }
                for (int k = 0; k < amountOfThreads; k++) {
                    threads.at(k).join();
                }

                threads.clear();

                //reading that vector baby and putting it into the img
                for (int l = 0; l < amountOfThreads; l++) {
                    //cout << "thest" << endl;
                    if (threadsOutcome.at(l)) {
                        int r = 0;
                        int g = 0;
                        int b = 0;
                        img << r << " " << g << " " << b << endl;
                        //cout << r << " " << g << " " << b << endl;
                    } else if (!threadsOutcome.at(l)) {
                        int r = 255;
                        int g = 255;
                        int b = 255; 
                        img << r << " " << g << " " << b << endl;
                        //cout << r << " " << g << " " << b << endl;
                    }
                }
                j = j + amountOfThreads - 1;
            } else {
                //doing the multiple
                
                for (int k = 0; k < sizeF*scaleFactor - j; k++) {
                    func(j+k, i, k);
                }
                
                //reading that vector baby and putting it into the img
                for (int l = 0; l < sizeF*scaleFactor - j; l++) {
                    //cout << "thest" << endl;
                    if (threadsOutcome.at(l)) {
                        int r = 0;
                        int g = 0;
                        int b = 0;
                        img << r << " " << g << " " << b << endl;
                    } else if (!threadsOutcome.at(l)) {
                        int r = 255;
                        int g = 255;
                        int b = 255; 
                        img << r << " " << g << " " << b << endl;
                    }
                }
                j = sizeF*scaleFactor - 1;
            }
        }
        
        double percent = i*100.0/sizeF;
        cout << percent << "%" <<  endl;
    }

    system("open picture.ppm");
    return 0;
} 

The issue I have right now is the error from line:

thread temp(func, j+k, i, k);

It says:

no matching constructor for initialization of 'std::thread'

What should I change to fix this?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Blenrine
  • 3
  • 2
  • 3
    Note that, beyond doing this as a learning exercise, you don't want a thread per pixel. Consider something like a thread pool capped at the number of physical cores you're running on, where you give each thread an entire *row* of pixels, and as each thread finishes, it takes the next available row. It takes time to context switch, you want threads to have a reasonable amount of work to do, ideally on a block of memory with high locality. – user229044 Apr 27 '23 at 14:46
  • Re, "[give] each of them a task...then want to wait for every thread to finish." That's inefficent. That means that threads that get an easy pixel will sit idle—doing nothing—waiting for the thread that was given the hardest pixel to finish. A better strategy would be to have each thread _immediately_ take another task as soon as it finishes the task it previously was working on. – Solomon Slow Apr 27 '23 at 14:53
  • Also, your program continually creates and destroys threads. That's expensive. If you do as I said above, then you'll be creating the threads once, at the start of the program, and you'll never join them until the program is completely finished. – Solomon Slow Apr 27 '23 at 14:55
  • Thx for the advice! This is my first time playing around with threads in cpp, so that's why it is not optimised yet. I want to eventually optimise it of course, but my first priority is getting something to work with multithreading. – Blenrine Apr 27 '23 at 15:01
  • the std:thread's templated constructor is made for taking **references** of arguments. But `j+k` cannot be used as a reference. I think, the compiler tells you the exact fingerprint of what argument types you provided and also suggests some candidates (of the constructor) that are *nearly* the same and may be usable for your desires ... at least gcc does this. Have a **really** close look into that error message, as it probably tells you exactly whats wrong ... but well burried between tons of other stuff. – Synopsis Apr 27 '23 at 15:08
  • @Synopsis `std::thread` uses forwarding references in the constructor, and those can bind to lvalues and rvalues. – NathanOliver Apr 27 '23 at 15:20
  • After fixing the issue from the answer to move `temp` into the vector your code compiles fine: http://coliru.stacked-crooked.com/a/fd5562f73a3b0899 – NathanOliver Apr 27 '23 at 15:22
  • You should always read [some documentation](https://en.cppreference.com/w/cpp/thread/thread) of the things you are attempting to use. – Jesper Juhl Apr 27 '23 at 16:03

1 Answers1

4

Threads are not copyable so you need to move the temp thread into the std::vector<std::thread>.

That means that

threads.push_back(temp); // tries to copy `temp` into the vector

should be

#include <utility>

// ...

threads.push_back(std::move(temp)); // uses the move constructor instead

Unrelated to your question: When you've fixed the above, the program compiles, but it has undefined behavior and crashes for me. Use AddressSanitizer, valgrind, gdb or some other tool to find out more.

Also unrelated to your question: Your computer has a limited number of hardware threads. Creating more threads than what's supported by hardware can be useful when the threads are mainly idle, waiting for some resource. Your threads don't fall into that category, so don't create more threads than supported by your hardware. It'll only add unnecessary context switching between the threads which will most likely slow things down. See std::thread::hardware_concurrency()

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108