Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>No, this will not work. It is broken.</p> <p>The problem has little/nothing to do with the compiler. It has to do with the order in which a second CPU will 'see' what the first CPU has done to memory. The memory (and caches) will be consistent, but the timing of WHEN each CPU decides to write or read each part of memory/cache is <em>indeterminate</em>.</p> <p>So for CPU1:</p> <pre><code>instance = new Singleton( instanceCreated ); instance-&gt;setInstanceCreated(); </code></pre> <p>Let's consider the compiler first. There is NO reason why the compiler doesn't reorder or otherwise alter these functions. Maybe like:</p> <pre><code>temp_register = new Singleton(instanceCreated); temp_register-&gt;setInstanceCreated(); instance = temp_register; </code></pre> <p>or many other possibilities - like you said as long as single-threaded observed behaviour is consistent. This DOES include things like " break up the ctor into two functions and call one part of it before the following call and the other part after."</p> <p>Now, it probably wouldn't break it up into 2 calls, but it would INLINE the ctor, particularly since it is so small. Then, once inlined, everything may be reordered, as if the ctor was broken in 2, for example.</p> <p>In general, I would say not only is it <em>possible</em> that the compiler reordered things, it is <em>probable</em> - ie for the code you have, there is probably a reordering (once inlined, and inlining is likely) that is 'better' than the order given by the C++ code.</p> <p>But let's leave that aside, and try to understand the real issues of double-checked locking. So, let's just assume the compiler didn't reorder anything. What about the CPU? Or more importantly CPU<em>s</em> - plural.</p> <p>The first CPU, 'CPU1' needs to follow the instructions given by the compiler, in particular, it needs to write to memory the things it has been told to write:</p> <ul> <li><code>instance</code>,</li> <li><code>instanceCreated</code></li> <li>other member variable of the Singleton (ie your Singleton does DO something, and has some state, doesn't it?)</li> </ul> <p>Actually, that 'other member variable' stuff is really important. Important for your singleton - that's its real purpose right?, and important for our discussion. So let's give it a name: <code>important_data</code>. ie <code>instance-&gt;important_data</code>. And maybe <code>instance-&gt;important_function()</code>, which uses <code>important_data</code>. Etc.</p> <p>As mentioned, let's assume the compiler has written the code such that these items are written in the order you are expecting, namely:</p> <ol> <li><p><code>important_data</code> - written inside the ctor, called from</p> <p><code>instance = new Singleton(instanceCreated);</code></p></li> <li><p><code>instance</code> - assigned right after new/ctor returns</p></li> <li><p><code>instanceCreated</code> - inside setInstanceCreated()</p></li> </ol> <p>Now, the CPU hands these writes off to the memory bus. Know what the memory bus does? IT REORDERS THEM. The CPU and architecture has the same constraints as the compiler - ie make sure this one CPU sees things consistently - ie single threaded consistent. So if, for example, <code>instance</code> and <code>instanceCreated</code> are on the same cache-line (highly likely, actually), they might be written together, and since they were just read, that cache-line is 'hot', so maybe they get written FIRST before <code>important_data</code>, so that that cache-line can be retired to make room for the cache-line where <code>important_data</code> lives.</p> <p>Did you see that? <code>instanceCreated</code> and <code>instance</code> were just committed to memory BEFORE <code>important_data</code>. Note that CPU1 doesn't care, because it is living in a single-threaded world...</p> <p>So now introduce CPU2:</p> <p>CPU2 comes in, sees <code>instanceCreated == true</code> and <code>instance != NULL</code> and thus goes off and decides to call Singleton::Instance()->important_function(), which uses <code>important_data</code>, which is uninitialized. CRASH BANG BOOM.</p> <p>By the way, it gets worse. So far, we've seen that the compiler could reorder, but we're pretending it didn't. Let's go one step further and pretend that CPU1 did NOT reorder any of the memory writing. Are we OK now?</p> <p>No. Of course not.</p> <p>Just as CPU1 decided to optimize/reorder its memory writes, CPU2 can <em>REORDER ITS READS</em>!</p> <p>CPU2 comes in and sees</p> <pre><code>if (!instanceCreated) ... </code></pre> <p>so it needs to read <code>instanceCreated</code>. Ever heard of 'speculative execution'? (Great name for a FPS game, by the way). If the memory bus isn't busy doing anything, CPU2 might pre-read some other values 'hoping' that instanceCreated is true. ie it may pre-read <code>important_data</code> for example. Maybe <code>important_data</code> (or the uninitialized, possibly re-claimed-by-the-allocator memory that will become <code>important_data</code>) is already in CPU2's cache. Or maybe (more likely?) CPU2 just free'd that memory, and the allocator wrote NULL in its first 4 bytes (allocators often use that memory for their free-lists), so actually, the memory soon-to-become <code>important_data</code> may actually <em>still be in the write queue of CPU2</em>. In that case, why would CPU2 bother re-reading that memory, when it hasn't even finished writing it yet!? (it wouldn't - it would just get the values from its write-queue.)</p> <p>Did that make sense? If not, imagine that the value of <code>instance</code> (which is a pointer) is 0x17e823d0. What was that memory doing before it became (becomes) the Singleton? Is that memory still in the write-queue of CPU2?...</p> <p>Or basically, don't even think about why it might want to do so, but realize that CPU2 might read <code>important_data</code> first, then <code>instanceCreated</code> second. So <em>even though CPU1 may have wrote them in order</em> CPU2 sees 'crap' in <code>important_data</code>, then sees <code>true</code> in <code>instanceCreated</code> (and who knows what in <code>instance</code>!). Again, CRASH BANG BOOM. Or BOOM CRASH BANG, since by now you realize that the order isn't guaranteed...</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload