It looks like you’re allocating on the audio thread here with all of those emplace_back calls. You really want to avoid allocating or using locks on the audio thread, which is very tricky in situations like this.
If it were me, I’d take an approach, as follows:
- Create a struct or class S which contains the
std::vector<float>, size, start and any other member data, all in one place.
- For thread safety, mark all instances of S which the audio thread accesses as const, or access them through a wrapper which marks them as const. (You can skip this if you need to, but then you lose the guarantee).
- When you want to resize them, copy the original (not on the audio thread), resize the copy, and then swap the new copy into the original place in a thread-safe way.
The last step is not easy (i.e. swapping it in in a thread safe way). You should take a look at these talks to see what you’re up against, and then look at the Farbot library for some prepackaged solutions.
I’ve tried to design something simpler than Farbot’s RealtimeObject before, which I’ll copy below in case it’s useful. If it works (it might not), it would allow you to do something like this:
struct S
{
std::vector<float> data;
int size, start, end; // whatever
}
std::array<ThreadSafe<S>, 8> sampleData;
// on audio thread
const auto reader = sampleData[0].get(); // must keep reader in scope!
for (const float sample : reader->data)
...
// on message thread
const auto copy = *sampleData[0].get();
copy.data->resize(); // ...
sampleData[0].set(copy);
Here is my ThreadSafe class. In theory it allows you to set and get from different threads at the time lock free, and as long you’re not setting from multiple threads, it shouldn’t crash. I’ve tested it a bit, but it’s not battle tested, and I can’t guarantee that there aren’t edge cases. (I’m eager for feedback, so if anyone spots any problems with it, please let me know!)
#include <atomic>
template <typename T>
class ThreadSafe
{
public:
class Reader
{
public:
~Reader() { --parent.readCount; }
const T& operator*() const
{
return writingToPrimary ? parent.secondary : parent.primary;
}
const T* operator->() const
{
return writingToPrimary ? &parent.secondary : &parent.primary;
}
private:
Reader(const ThreadSafe& p, const bool w) :
parent(p), writingToPrimary(w) {
}
const ThreadSafe& parent;
const bool writingToPrimary;
friend class ThreadSafe;
};
ThreadSafe(const T& t = T()) : primary(t), secondary(t),
writingToPrimary(true), readCount(0) {}
Reader get() const
{
++readCount;
return Reader(*this, writingToPrimary);
}
void set(const T& t)
{
if (writingToPrimary)
primary = t;
else
secondary = t;
writingToPrimary.exchange(!writingToPrimary);
while (readCount); // spin until all readers have finished
}
private:
T primary, secondary;
mutable std::atomic_bool writingToPrimary;
mutable std::atomic_int readCount;
friend class Reader;
};