<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Multicore on Roberto Selbach</title><link>https://rselbach.com/tags/multicore/</link><description>Recent content in Multicore on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 21 Sep 2006 19:26:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/multicore/index.xml" rel="self" type="application/rss+xml"/><item><title>Const Does Not Mean Concurrent</title><link>https://rselbach.com/const-does-not-mean-concurrent/</link><pubDate>Thu, 21 Sep 2006 19:26:00 +0000</pubDate><guid>https://rselbach.com/const-does-not-mean-concurrent/</guid><description>&lt;p&gt;A cache lookup crashed under load today even though every worker called a &lt;code&gt;const&lt;/code&gt; method. That sentence sounds reassuring only if &lt;code&gt;const&lt;/code&gt; is mistaken for a locking primitive.&lt;/p&gt;
&lt;p&gt;The method updated a mutable cache:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QImage ImageStore&lt;span style="color:#f92672"&gt;::&lt;/span&gt;image(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;key) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;m_cache.contains(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_cache.insert(key, loadImage(key));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;const&lt;/code&gt; prevents ordinary modification through the public type. It says nothing about simultaneous calls, and &lt;code&gt;mutable&lt;/code&gt; explicitly allows internal state to change. Two processors can both inspect and modify the cache, corrupting its internal bookkeeping.&lt;/p&gt;
&lt;p&gt;I first added a mutex around the whole operation. That was correct but serialised disk loading, so unrelated images waited behind one slow file. The improved version checks under lock, loads outside it, then locks again to publish:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QImage ImageStore&lt;span style="color:#f92672"&gt;::&lt;/span&gt;image(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;key) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.lock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (m_cache.contains(key)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QImage image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; image;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QImage image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; loadImage(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.lock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;m_cache.contains(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_cache.insert(key, image);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; m_cache.value(key);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_mutex.unlock();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; image;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Duplicate loading is possible, but publication is safe and the common cached path is short. For expensive loads I might track in-progress keys with a wait condition; that complexity needs measurements first.&lt;/p&gt;
&lt;p&gt;Implicitly shared Qt values make returning an image cheap in the usual case, but they do not make shared storage safe for concurrent mutation. Copy-on-write protects value semantics, not arbitrary access patterns.&lt;/p&gt;
&lt;p&gt;The lock itself belongs beside the state it protects. Passing the cache to helper code without the mutex invites a second access path that quietly ignores the rule. I am documenting the invariant as &amp;ldquo;all cache lookup and publication occurs while holding &lt;code&gt;m_mutex&lt;/code&gt;,&amp;rdquo; then checking each method against that sentence.&lt;/p&gt;
&lt;p&gt;I prefer immutable inputs and per-job results when using several cores. Shared caches should be small, explicitly locked, and treated as an optimisation rather than the foundation of correctness. The caveat is memory and copying: some data is too large to duplicate, so careful shared ownership remains necessary.&lt;/p&gt;
&lt;p&gt;The failure appeared only on the dual-core machine and only after several minutes. That does not make it a rare bug. It makes the single-core test machine an excellent accomplice.&lt;/p&gt;</description></item></channel></rss>