Thread protected variables from inside a threaded function

241 views Asked by At

I writing a simple raytracer plugin for a software that generates the pixels from a multithreaded function. the pseudo code is

Scene* scene;

void engine_(int y, int, x, pixel& out){ // this function is threaded for each orizontal line y

Buffer* line;
my_render_engine(y, scene, buffered_line); // here is where I'm accessing a lot of shared data, incurring into data racing
out = *buffered_line; // here I have just a line of pixels
}

My Scene* contains many shared variables (camera, samplers, shaders, etc) and the renderer only need read access to them. These have been generated before the engine_ is called. Accessing them concurrently creates undefined behaviours.

I have grabbed a thread_pool template trying to protect my threads like this

void engine\_(int y, int, x, pixel& out){ // this function is threaded for each orizontal line y

m.lock()
Buffer\* line(width);
auto render_line = \[this\](int y, int x, Buffer\* buffer) {
Scene thread_scene = Scene();
... creating scene ...

            }
    
            thread_world.camera_ptr->my_render_engine(y, x, r, thread_scene , buffer_image_ptr);
        };
    
        //for (int j = 0; j < height; j++)
        m_threadPool.AddTask(render_line, j, x, r, buffer_image_ptr); // running render function for each  
                                                                      //thread safely
    m.unlock()
    out = *buffered_image;  // here I have my entire image

}

this kinda works or at least I'm able to protect my variables. it's very inefficient in many ways and needs improvements, like generating just the right amount of scene as my number of cores. The big problem is that I cannot visualize what I'm doing unless I wait to generate the entire image, which I cannot do. The engine_ function let me visualize each row of pixels as soon as it is generated, but I have to let it do it. Using my thread pool as it is doesn't work obviously, because I'm overriding engine_ in easy words.

A temporary solution is to create a brand new scene everytime I run the renderer like this

void engine_(int y, int, x, pixel& out){ // this function is threaded for each orizontal line y    
    Scene* thread_scene;    .....creating scene.....    Buffer* line(width);    
    my_render_engine(y, 3dscene, buffered_line); // here is where I'm accessing a lot of shared data, incurring into data racing    
    out =  *buffered_line; // here I have just a line of pixels 
}

it is similar to the thread_pool but it let me visualize the image I'm generating line by line. The problem is that is not well thread protected. inside the scene, there are shaders and 3dsamplers that are shared between many 3d objects like cameras, lights and geometries.

So here is where I need help:

I 'think' that when I'm creating my scene, the pointers of the variables inside it are shared between the threads and when the threads access them concurrently the program crashes:

class Scene{
Camera* cam;
Sampler* sampler;
Object_list<object*> objs;
Tracer* tracer_ptr;
Shader_list<shader*> shaders
}; // these pointers refer to the same stuff

My questions are: ->Am I right? even though I'm creating several copies of the scenes I'm not protecting their variables? -> if that's the case, how can I make these variables protected? hopefully I don't need to copy them (especially the mesh_ptr which are quite heavy.

2

There are 2 answers

0
ytrox On BEST ANSWER

I've managed to solve the issue by creating a map containing the needed data for each thread and a thread::id. the data is the World class which contains pointers for the shared read-access data and a copy for what is not (hopefully is thread safe, but I'm not entirely sure).

        const std::thread::id tID = std::this_thread::get_id();
        m_lock.lock();
        std::map<const std::thread::id, World*>::iterator thread_it = worlds_map.find(tID);


        if (thread_it == worlds_map.end())
        {
            //creating new scene for thread map
            World* thread_world = new World();
            thread_world->set_bg_texture(input_texture_ptr);
            thread_world->build();
            .........

            // adding to thread map
            worlds_map[tID] = thread_world;
        }

        thread_it = worlds_map.find(tID);

        m_lock.unlock();
        thread_it->second->camera_ptr->render_scene(y, x, r, buffer_image_ptr, *thread_it->second);
    }```

this way I only need to create an instance for each thread my cpu has and not for each row
0
Wisblade On

I would go first of all for a std::shared_mutex used as a read-write mutex.

When you access your shared variables for reading, you'll use the lock_shared, unlock_shared, try_lock_shared primitives - and, obviously, you don't write to these variables!

When you access them for writing, you'll use usual lock, unlock and try_lock primitives to gain an exclusive access - and then, you can write them safely.

If after that your problem isn't solved, then you probably have something else corrupted within your code.