3

Environment is Ubuntu 24.04, QT Creator 6, g++ 13.3, pthreads.

Given the following code (error handling has been removed for readability):

#include <iostream>
#include <thread>
#include <cstring>

#define HEAPBUFFER  (100000)

class Foo {
public:
    Foo() {
        foo_buffer = nullptr;                           //Construct to null
    }
    ~Foo(){
        std::cout<<"Deleting instance heap"<<std::endl;
        delete [] foo_buffer;                           //Release the heap
    }
    void take_copy(u_int8_t *heap_to_copy, int len) {
        foo_buffer = new u_int8_t[len];                 //Allocate on the heap
        buf_len = len;                                  //Store the length internally

        //At this point here, if this is running in one or more seperate threads, does this need to have mutex protection locked?
        std::memcpy(foo_buffer, heap_to_copy, buf_len); //Copy from the "global" heap allocation to the "local" heap allocation
        //At this point here, if this is running in one or more seperate threads, does this need to have mutex protection released?
    }

private:
    u_int8_t    *foo_buffer;
    int buf_len;
};

u_int8_t    *heap_buffer;


int main()
{
    Foo foo;
    Foo bar;

    heap_buffer = new u_int8_t[HEAPBUFFER];                         //Allocate on heap, sets to 0x00

    std::memset(heap_buffer, 0xFF, sizeof(u_int8_t)*HEAPBUFFER);    //Set to 0xFF

    std::thread take_copy_one(&Foo::take_copy, &foo, heap_buffer, HEAPBUFFER);                         //Take a copy for foo to use
    std::thread take_copy_two(&Foo::take_copy, &bar, heap_buffer, HEAPBUFFER);                         //Take a copy for foo to use

    //If I decide to do something here to heap_buffer, I'm guess there needs to be some shared mutex protection or bad things may happen?
    take_copy_one.join();
    take_copy_two.join();

    delete [] heap_buffer;                                          //Release the memory

    return 0;
}

I have allocated heap memory, I then instantiate two objects of "Foo". These two objects require a copy of the heap allocated memory in order to perform some computation. In this simple example, the heap is untouched in the main thread as my question here relates purely to the std::memcpy function.

I am aware that in the real world, the main thread may well change the heap memory and would need some kind of synchronisation across the threads, but I'm ignoring that in this instance.

However, my questions to those cleverer than me are:

  1. In the case of the code above, would the std::memcpy call in the two thread require a mutex around them?
  2. If a mutex is required, this would need to be a shared mutex, i.e. I couldn't just use an instance level mutex, it would have to be declared "globally" and a reference to it passed to the instance, either via the constructor or the take_copy method?

I'm very unclear on this. My thoughts are that, because it is a copy, i.e. the shared heap memory is being read (not written), then the two std::memcpy calls will be "safe".

7
  • For what here any synchronisation, this is unclear. Synchronize what? Commented Sep 20 at 13:56
  • 2
    You will need mutex/shared_mutex to make copying safe, I still see a lot of "C" style coding and use of global variables (which is extra dangerous in multithreaded code). So I did a code review for you and left a lot of comments here : godbolt.org/z/a4544W1h6 Commented Sep 20 at 14:38
  • @PepijnKramer Just going through it now. Thankyou so much. Commented Sep 20 at 14:42
  • 6
    The threads are only reading. There is no modification of shared data between launching the threads and joining them. This is safe. Data races only occur when there is a concurrent modification Commented Sep 20 at 14:52
  • Why is heap_buffer global? Commented Sep 20 at 14:57

1 Answer 1

5
  1. In the case of the code above, would the std::memcpy call in the two thread require a mutex around them?

No.

As long as there is a guarantee that what the reading threads read will not be written to while the reading is done, any number of threads can do the reading simultaneously without extra synchronization. This goes for std::memcpy as well as loops doing the reading piecewise. What you've demonstrated in your example is therefore safe.

Sign up to request clarification or add additional context in comments.

5 Comments

The fundamental issue then is guaranteeing that no thread will write to the buffer while any other thread reads from the buffer. Simply being careful in your programming is not such a guarantee.
@JimRogers That's however not hard. The OP has a case where there's no doubt.
The code he shows initializes the buffer to all zeros. That buffer is copied to two or more threads. If the buffer needs to be initialized then why copy the buffer at all? Why not make an independent buffer in each thread? If threads update the buffer and write the update back to the original buffer then synchronization is required so that writes to not overlap with other writes or with reads.
@JimRogers No, the buffer is default initialized (leaving every u_int8_t with an indeterminate value) and then 0xFF is written to every u_int8_t, but that's of little importance. It's a simplification of the real thing. Example: If you have a threaded min/max algorithm you'd copy the state and then work on it. You can't then know what state to initialize the buffer with but will have to copy it and only sub-trees will then need to know the state to copy and there is no upwards sync needed. If the code in the Q is a simplified part of such a sub-tree, it is perfectly fine as-is.
Just for reference. The code I supplied is very much a simplification of what I'm doing in the real world as I wasn't sure about the memcpy within different threads. In the real world I will have a "global" heap allocation (I can't change that) which contains Image data (each u_int_8 is a grayscale value), this data is then JPG compressed (one thread) and H264 encoded to MP4 (another thread). For performance reasons, these need to run Asynchronously with a copy of the original data. There WILL need to be Synchronisation, but my original query was purely about memcpy and threads. Thankyou.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.