Lock-free cache implementation in C++11

ぃ、小莉子 提交于 2019-12-06 11:59:36

Consider something as:

class C {
public:
   double getValue() {
      if (alreadyCalculated == true)
         return m_val;

      bool expected = false;
      if (calculationInProgress.compare_exchange_strong(expected, true)) {
         m_val = calculate(m_param);
         alreadyCalculated = true;
      // calculationInProgress = false;
      }
      else {
     //  while (calculationInProgress == true)
         while (alreadyCalculated == false)
            ; // spin
      }
      return m_val;
   }

private:
   double m_val;
   std::atomic<bool> alreadyCalculated {false};
   std::atomic<bool> calculationInProgress {false};
};

It's not in fact lock-free, there is a spin lock inside. But I think you cannot avoid such a lock if you don't want to run calculate() by multiple threads.

getValue() gets more complicated here, but the important part is that once m_val is calculated, it will always return immediately in the first if statement.

UPDATE

For performance reasons, it might also be a good idea do pad the whole class to a cache line size.

UPDATE 2

There was a bug in the original answer, thanks JVApen to pointing this out (it's marked by comments). The variable calculationInProgress would be better renamed to something as calculationHasStarted.

Also, please note that this solution assumes that calculate() does not throw an exception.

std::atomic is not guaranteed to be lock-free, though you can check on std::atomic<T>::is_lock_free() or std::atomic::is_always_lock_free() to see if your implementation can do this lockfree.

Another approach could be using std::call_once, however from my understanding this is even worse as it is meant to block the other threads.

So, in this case, I would go with the std::atomic for both m_val and alreadyCalculated. Which contains the risk that 2 (or more) threads are calculating the same result.

Just to answer the one technical question here: To make sure the value is updated before the flag, you update the flag with release semantics. The meaning of release semantics is that this update must (be seen as) happen after all previous ones. On x86 it only means a compiler barrier before the update, and making the update to memory, not register, like this:

asm volatile("":::"memory");
*(volatile bool*)&m_alreadyCalculated = true;

And this is exactly what the atomic set is doing in release semantics

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!