Load Barriers
There is one final, vital matter to be aware of.

That a physical core has actually written a value to memory - actually, really, genuinely has done so, because we cast the necessary magic runes with store barriers and atomic writes to guarantee that it is so - this still does not mean other physical cores will actually be using the values which were written.

The problem is that other physical cores, in a load-side mirror image of store buffers, are never guranteed to actually be reading the values which are currently in memory. They may continue to use values they read earlier and have cached for an indefinite period of time.

It's all fun in processor land.

This behaviour is caused by the invalidation queue. When a write to memory occurs, any physical core which has in its caches local copies of the values in memory which have been written to receives a message, an invalidation request, telling it that its local copies are now out of date - but these messages may not be processed in a timely manner (they queue up, in the invalidation queue), and so the physical core can continue to use the earlier, incorrect copy that it already holds for an indefinite period of time.

If we consider the linked list example, it would mean that even though we in our new element set the user data, then issued a store barrier, then used an atomic operation to link the new element into the list, another physical core might already hold an earlier copy of the user data (perhaps we used that memory for something else earlier on, or maybe the OS cached some data in the background we didn't know about) and so when it comes to access the new element in the list and the user data in that element, it will end up using that earlier copy of the user data because it has not yet noticied the invalidation request telling it that its local copy is out of date. Catastrophe/hilarity, etc.

This is where load barriers come in.

The and the only thing a load barrier does, is make it such that when the physical core finally does complete a load from memory (which it may still never do, so none of these loads necessarily happen), all those load operations issued before the load barrier will be completed (which is to say, the invalidation request queue will be fully processed, to ensure this is so) before those load operations which were issued after the load barrier.

In short, issuing a load barrier ensures that when a load finally completes after the barrier, that the invalidation request queue will prior to that completion be serviced, which in turn ensures that physical core really will see whatever completed stored have been published by other cores.

So before we as a thread reading the linked list access the void pointer of user data, we must always issue a load barrier; this forces the invalidation request queue to be serviced before we read the user data.

(It's worth remembering that processors are much better at deferring stores than they are loads; a store can be held in the store buffers and the processor can keep going, but a load means the processor really has to go and find out what that value is to be able to do any work which uses that value, which of course means if you issued a load barrier beforehand, that the processor will end up having to honour that load barrier so there's no need, as there is with store barriers, to perform an atomic operation to force the load barrier to be honoured.)

Full Barriers
A full barrier is a combination of a load barrier and a store barrier. They exist mainly for API completeness, I think - I think I've used one once, as I normally need only load or store, depending on what I'm doing in the code.