Example of AbstractFIFO of ReferenceCountedObject objects?


#1

Hi!

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?

Thanks!


#2

Instead of using AbstractFIFO you can use a plain old std::list<> and wrap access to it with a CriticalSection.


#3

Unless you're worried about locks of course...


#4

Hi!

I think that's kind of what I'm doing:

ReferenceCountedArray<OSC_Message, CriticalSection> m_MessageQueue; (Where OSC_Message inherits ReferenceCountedObject).

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.


#5

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);
};

#6

Thanks!

I'll give it a try and see whan performance difference I get when using lock-free access!


#7

@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?