In my quest to properly implement multithreading in my program I've realized that a good, scalable and future proof solution seems to be Juce's AbstractFIFO, made to use ReferenceCountedObjects somehow. I should then derive my own payload objects from ReferenceCountedObject, and thus be able to use them in AbstractFIFO.
Looking at the code of AbstractFIFO scares me a bit, my C++ - fu simply doesn't seem to be up to scratch; I don't really understand how to do the above, and the only discussion I've found on the forum turned into a discussion about some similar functionality in vflib that now seems to be unsupported.
Does anyone have an example of an AbstractFIFO implementation containing ReferenceCountedObjects they would care to share?
I add to the end, and always take out the element at index 0, if there is one. Maybe somewhat inefficient depending on the internal implementation of ReferenceCountedArray when shifting list items around, but I haven't seen any multithreading errors here.
The messages I am dealing with are time critical so I don't want to introduce jitter or delays due to locking, but I also cannot use the built in MIDI tools of Juce, my messages being OSC.
From browsing and posting in the forum I've found recommendations of using lock-free FIFO queues as being the best option for this, especially when scaling up to high amounts of CPU activity.
I discussed this in an older thread (which was destroyed, by offtopic comments) this doesn’t use reference counted pointers but it should be easy to change.
Its very similar to AbstractFifo but uses only pointers, and its lock free.
template <class ObjectClass>
class CircularSingleWriterSingleReaderLockFreePointerFIFO
{
public:
CircularSingleWriterSingleReaderLockFreePointerFIFO (int capacity) noexcept
{
jassert (capacity > 0);
arrayOfPointers.resize(capacity);
}
~CircularSingleWriterSingleReaderLockFreePointerFIFO() {};
int getTotalSize() const noexcept { return arrayOfPointers.size(); }
int getFreeSpace() const noexcept { return arrayOfPointers.size() - getNumReady(); }
int getNumReady() const noexcept
{
const int vs = validStart.get();
const int ve = validEnd.get();
return ve >= vs ? (ve - vs) : (arrayOfPointers.size() - (vs - ve));
}
void reset() noexcept
{
validEnd = 0;
validStart = 0;
}
void setTotalSize (int newSize) noexcept
{
jassert (newSize > 0);
reset();
arrayOfPointers.resize(newSize);
}
//==============================================================================
// adds a Pointer to the FIFO, return true when successful
bool write ( ObjectClass* object) noexcept
{
const int vs = validStart.get();
const int ve = validEnd.value;
const int freeSpace = ve >= vs ? (arrayOfPointers.size() - (ve - vs)) : (vs - ve);
if (freeSpace <= 1)
{
return false;
}
arrayOfPointers.set(ve,object);
int newEnd = ve + 1;
if (newEnd >= arrayOfPointers.size()) newEnd=0;
validEnd.set(newEnd); // ATOMIC at the end
return true;
}
// reads a pointer from the FIFO, returns the pointer or 0 if not successful
ObjectClass* read () noexcept
{
const int vs = validStart.value;
const int ve = validEnd.get();
const int numReady = ve >= vs ? (ve - vs) : ( arrayOfPointers.size() - (vs - ve));
if (numReady <= 0)
{
return 0;
}
int newStart = vs + 1;
if (newStart >= arrayOfPointers.size()) newStart = 0;
validStart.set(newStart); // ATOMIC at the end
return arrayOfPointers[vs];
}
private:
//==============================================================================
Atomic <int> validStart, validEnd;
Array <ObjectClass*> arrayOfPointers;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CircularSingleWriterSingleReaderLockFreePointerFIFO);
};
@chkn why did you use validStart.value; validEnd.get(); for this? Why does one use the getter, and the other directly access the underlying value? Why not the underlying value for both, or the getter for both?