2
\$\begingroup\$

I need to figure out the best way to deal with memory pre-allocation

Below is pseudo-code for what I am doing now, and it seems to be working fine.

I am sure there is a better way to do this, I would like to see if anyone has any good idea.

In Thread A I need to allocate 300 MB of memory and zero it out:

Thread A:

char* myMemory = new char[10*30*1024*1024];

In Thread B I incrementally get 10 sets of data 30 MB each. As I get this data it must be written to memory

Thread B:

int index1 = 0;
char* newData = getData(...); // get a pointer to 30 MB of data
memcpy(&myMemory[index1*30*1024*1024], &newData[0], 30*1024*1024];
SetEvent(...) // tell Thread C that I have written new data

// use index i as circular Buffer
if (index1 < 10)
 index1++;
else
 index1 = 0;

In Thread C, when new data is written, I need to get it and process it

Thread C:

int index2 = 0;
char* dataToProcess[] = new char[30*1024*1024];
if (event Fires) // if event in Thread B is set
 memcpy(&dataToProcess[0], &myMemory[index2*30*1024*1024], 30*1024*1024];
 processData(dataToProcess)

// use index i as circular Buffer
if (index2 < 10)
 index2++;
else
 index2 = 0;
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Wait, if it seems to be working fine, then what exactly are you looking for? I'm confused... \$\endgroup\$
    – user541686
    Commented Apr 9, 2011 at 1:06
  • \$\begingroup\$ I think it is bad design and it will be hard for others to edit. I can't think of any other way to do it though. My main concern is the indexing independent in the two threads. \$\endgroup\$
    – rossb83
    Commented Apr 9, 2011 at 1:09
  • \$\begingroup\$ perhaps, I was not aware of this website. \$\endgroup\$
    – rossb83
    Commented Apr 9, 2011 at 1:18
  • \$\begingroup\$ @rossb83: Yeah no worries, it's relatively new. If you need advice on writing a particular piece of code (like here), that's where you should probably post it. :) \$\endgroup\$
    – user541686
    Commented Apr 9, 2011 at 1:22
  • \$\begingroup\$ I don't see the point to Thread A. You could just as easily allocate the 300MB in B, fill in the data, zeroing out any padding, and pass it on to C. You can't do anything in B & C until A is done anyway. \$\endgroup\$
    – Duck
    Commented Apr 9, 2011 at 1:24

3 Answers 3

2
\$\begingroup\$

Another approach I can think of could be this. Get rid of myMemory altogether. Once you receive data in thread B, allocate memory and copy that data (as you're doing now). Send thread C a message that data is received. Once thread C copies the data into its internal buffer, thread C can send message back to thread B that the data has been copied. Thread B then deallocate the buffer.

Your design seems fine to me. Yet another approach could be avoiding the data copy in thread C.

int index2 = 0;
char* dataToProcess;
if (event Fires) // if event in Thread B is set
 dataToProcess = &myMemory[index2*30*1024*1024];
 processData(dataToProcess)
// Rest of the code
\$\endgroup\$
1
  • \$\begingroup\$ Suggestion: if Thread B has receieved a vacated memory buffer from Thread C, and if Thread B still has work to do (loop count hasn't reached the limit), Thread B can reuse the memory buffer for new data. This is the idea in multithreaded resource pooling. \$\endgroup\$
    – rwong
    Commented Apr 9, 2011 at 5:56
1
\$\begingroup\$

I'm not sure how your threads work, but it looks like you need some more synchronization.

If B and C are a pair, I don't see indexing getting out of control, but have you thought about out-of-order execution and reordering of memory writes? B's CPU might execute code firing the event before completing the write to myMemory, resulting in C reading partial data. Granted, it may be highly unlikely or downright impossible in your case, but it's something to think about. You may need a memory barrier or a higher-level synchronization concept.

In fact, why aren't you using a message queue? Or something simpler, like a circular buffer? (Incidentally, boost::circular_buffer's thread-safe bounded buffer example looks very similar to your question.)

Or if you could attach the 30 MB data to the events, it would be a very simple solution.

\$\endgroup\$
2
  • \$\begingroup\$ The problem with boost circular buffers is that the memory has to be preallocated in Thread A. I wanted to use boost circular buffers. Is there a way to preallocate the memory as well as use a circular buffer? \$\endgroup\$
    – rossb83
    Commented Apr 9, 2011 at 1:52
  • \$\begingroup\$ Can't thread A simply create the object? \$\endgroup\$
    – aib
    Commented Apr 9, 2011 at 3:13
1
\$\begingroup\$

you could use an approach like this (pseudocode);

template < typename T, size_t BlockSize_ >
class t_lockable_array {
public:
    static const size_t BlockSize = BlockSize_;
    typedef T t_object;

/* ...stuff... */

    void copyElements(std::vector& dest, const t_range& range) {
        t_lock_scope lock(this->mutex());
        /* copy @a range to @a dest here... */
    }

/* ...stuff... */

private:
    t_mutex d_mutex; /* assumption in this example: lock/unlock is a mutation */
    t_object d_object[BlockSize];
};

template < typename T, size_t BlockSize_, size_t BlockCount_ >
class t_partitioned_array {

public:
    static const size_t BlockSize = BlockSize_;
    static const size_t BlockCount = BlockCount_;
    static const size_t ObjectCount = BlockCount * BlockSize;
    typedef T t_object;

/* ...stuff... */

    t_lockable_array<t_object, BlockSize>& at(const size_t& idx) {
    /*
    direct access: each partition handles its locking/interface.
    alternatively, you could rewrap the lockable array's
    interface if you prefer encapsulation here.
    */
        return this->d_array[idx];
    }

/* ...stuff... */

private:
    t_lockable_array<t_object, BlockSize> d_array[BlockCount];
};

then just create a t_partitioned_array<char,10,ThirtyMB> on thread A

on thread B:

t_partitioned_array<char,10,ThirtyMB>& array(this->getSharedArray());
std::vector<char>& localBuffer(this->getLocalBuffer());

// the accessed partition will lock itself during read/write
array.at(this->partitionToRead()).copyElements(localBuffer, this->rangeOfMemoryToRead());

this->processData(localBuffer);
\$\endgroup\$
1
  • \$\begingroup\$ This is not what I would call pseudocode \$\endgroup\$
    – raven
    Commented Apr 10, 2011 at 21:09

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.