The structure declares the read and write indices like so:
std::atomic<IndexT> read_idx;
std::atomic<IndexT> write_idx;
These two variables are going to be stored next to each other in memory. If IndexT is say a 32-bit int, then the read_idx and write_idx will be 4 bytes each. A cache line is 64 bytes on intel, so these two variables are going to end up on the same cache line.The problem with this, is that cache coherence protocols work on the granularity of lines, not bytes. So when one core writes to anywhere on the line (for example the producer to write_idx), then the other core (say the consumer core) has to invalidate its cache entry for that line, and get the new value off the bus, or maybe even hit memory.
Regardless of the specifics, the point is the consumer and producer cores can no longer independently write to their respective variables. Whenever they write to their variable, it will cause some cache coherence operations to be done to update the line that variable is in, in the other core.
This is false sharing, since the producer and consumer don't actually need to access the others variable. The cores are just forced to share access to the variables because the variables are on the same cache line.
This can really have a big impact for multithreaded workloads, and bring down the overall thread throughput.
One solution is to add some padding between the variables. Something like:
std::atomic<IndexT> read_idx;
unsigned char pad[64];
std::atomic<IndexT> write_idx;
will do. That will put the variables on different cache lines. And you might want to pad out some other variables to different cache lines as well.