Double check lock error

The application scenario of double check lock:
the method of initializing an object in a function in multiple threads and calling the method of the object

The use of double check lock
is to ensure that the object exists to call it, and to reduce the granularity of the lock when the program logic is correct

Then, what are
the shortcomings or errors of the double-check lock that was criticized by posterity ?

void undefined_behaviour_with_double_checked_locking()
{
    
    
  if(!resource_ptr)  // 1
  {
    
    
    std::lock_guard<std::mutex> lk(resource_mutex);
    if(!resource_ptr)  // 2
    {
    
    
      resource_ptr.reset(new some_resource);  // 3
    }
  }
  resource_ptr->do_something();  // 4
}

As we can see, the first time to check whether resource_ptr has data, after locking, check whether resource_ptr has data again, construct new data in resource_ptr, and then use smart pointers containing new data to perform certain operations.

There are potential competitions in places 1, 3 here, and at 3 places, a some_resource object is constructed first, and then we dive into the reset source code.

  template<typename _Yp>
	_SafeConv<_Yp>
	reset(_Yp* __p) // _Yp must be complete.
	{
    
    
	  // Catch self-reset errors.
	  __glibcxx_assert(__p == 0 || __p != _M_ptr);
	  __shared_ptr(__p).swap(*this);
	}

An anonymous object is used to construct __shared_ptr and __shared_ptr itself for swap exchange,

void
      swap(__shared_ptr<_Tp, _Lp>& __other) noexcept
      {
    
    
	std::swap(_M_ptr, __other._M_ptr);
	_M_refcount._M_swap(__other._M_refcount);
      }

Here, the pointers are exchanged first, and then the reference counts are exchanged. In theory, when the exchange of pointers in this step is completed, resource_ptr already satisfies resource_ptr!=nullptr, and another thread immediately executes resource_ptr->do_something(). . However, in the process of new, cpu out-of-order problems may occur , that is, the address is allocated first and then the construction operation is performed. As a result, the resource_ptr actually obtains a pointer that has not been constructed. Yes, at this time, another thread looks at it. It is a wrong pointer . Not only may the function called internally by this pointer go wrong, it may also access wrong (uninitialized) data.

In order to improve performance, the uncertainty of the program is caused, which is really regrettable.

Reference from "C++ Concurrent Programming Practice"

Guess you like

Origin blog.csdn.net/adlatereturn/article/details/109133084