c++之std::unique_lock, std::lock, std::scoped_lock及std::condition_variable

1. std::unique_lock

unique_lock与lock_guard主要区别在于

  1. unique_lock可用的第二个参数有三种,而lock_guard只有一种
  2. unique_lock的析构需要判断_M_owns,而lock_guard无需判定
  3. 条件变量的传参为unique_lock

说什么都不如源码来的简单明了

1.1 源码分析

  template<typename _Mutex>
    class unique_lock
    {
    public:
      typedef _Mutex mutex_type;

      unique_lock() noexcept
      : _M_device(0), _M_owns(false)
      { }
---------------------------------------------------------------------------------------------------
1. unique_lock传入mutex之后,会调用lock(),并将_M_owns设置为true,后续析构时判断
      explicit unique_lock(mutex_type& __m)
      : _M_device(std::__addressof(__m)), _M_owns(false)
      {
	lock();
	_M_owns = true;
      }
---------------------------------------------------------------------------------------------------
_M_owns实则被unique_lock用来表示是否上锁的状态,如果是true则上锁,需要后续析构解锁,否则,则不需要解锁
2. 当defer_lock_t时,_M_owns为false,则析构时不会解锁
3. 当try_to_lock_t时,_M_owns(_M_device->try_lock()),则要根据try_lock结果判断是否时true,再判断析构是否解锁
4. 当adopt_lock_t时,_M_owns为true,则析构时解锁

      unique_lock(mutex_type& __m, defer_lock_t) noexcept
      : _M_device(std::__addressof(__m)), _M_owns(false)
      { }

      unique_lock(mutex_type& __m, try_to_lock_t)
      : _M_device(std::__addressof(__m)), _M_owns(_M_device->try_lock())
      { }

      unique_lock(mutex_type& __m, adopt_lock_t) noexcept
      : _M_device(std::__addressof(__m)), _M_owns(true)
      {
	// XXX calling thread owns mutex
      }
---------------------------------------------------------------------------------------------------

      template<typename _Clock, typename _Duration>
	unique_lock(mutex_type& __m,
		    const chrono::time_point<_Clock, _Duration>& __atime)
	: _M_device(std::__addressof(__m)),
	  _M_owns(_M_device->try_lock_until(__atime))
	{ }

      template<typename _Rep, typename _Period>
	unique_lock(mutex_type& __m,
		    const chrono::duration<_Rep, _Period>& __rtime)
	: _M_device(std::__addressof(__m)),
	  _M_owns(_M_device->try_lock_for(__rtime))
	{ }
---------------------------------------------------------------------------------------------------
9.  ~unique_lock()这里析构函数就是判断_M_owns,是否为true,如果是则解锁
 
      ~unique_lock()
      {
	if (_M_owns)
	  unlock();
      }
---------------------------------------------------------------------------------------------------
      unique_lock(const unique_lock&) = delete;
      unique_lock& operator=(const unique_lock&) = delete;
---------------------------------------------------------------------------------------------------
 5. move construct与move copy
需要注意的是move copy的实现方式,先unlock,再搞个临时变量swap,这种处理方式类似与vector<int>().swap(...)

      unique_lock(unique_lock&& __u) noexcept
      : _M_device(__u._M_device), _M_owns(__u._M_owns)
      {
	__u._M_device = 0;
	__u._M_owns = false;
      }

      unique_lock& operator=(unique_lock&& __u) noexcept
      {
	if(_M_owns)
	  unlock();

	unique_lock(std::move(__u)).swap(*this);

	__u._M_device = 0;
	__u._M_owns = false;

	return *this;
      }
---------------------------------------------------------------------------------------------------
 6. lock()函数实现,_M_device->lock(),实际是mutex.lock()
 7. try_lock()函数是先判断_M_owns,然后再lock()
 8. try_lock_until()try_lock_for()则是加了个时间chrono

      void
      lock()
      {
	if (!_M_device)
	  __throw_system_error(int(errc::operation_not_permitted));
	else if (_M_owns)
	  __throw_system_error(int(errc::resource_deadlock_would_occur));
	else
	  {
	    _M_device->lock();
	    _M_owns = true;
	  }
      }

      bool
      try_lock()
      {
	if (!_M_device)
	  __throw_system_error(int(errc::operation_not_permitted));
	else if (_M_owns)
	  __throw_system_error(int(errc::resource_deadlock_would_occur));
	else
	  {
	    _M_owns = _M_device->try_lock();
	    return _M_owns;
	  }
      }

      template<typename _Clock, typename _Duration>
	bool
	try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime)
	{
	  if (!_M_device)
	    __throw_system_error(int(errc::operation_not_permitted));
	  else if (_M_owns)
	    __throw_system_error(int(errc::resource_deadlock_would_occur));
	  else
	    {
	      _M_owns = _M_device->try_lock_until(__atime);
	      return _M_owns;
	    }
	}

      template<typename _Rep, typename _Period>
	bool
	try_lock_for(const chrono::duration<_Rep, _Period>& __rtime)
	{
	  if (!_M_device)
	    __throw_system_error(int(errc::operation_not_permitted));
	  else if (_M_owns)
	    __throw_system_error(int(errc::resource_deadlock_would_occur));
	  else
	    {
	      _M_owns = _M_device->try_lock_for(__rtime);
	      return _M_owns;
	    }
	 }

      void
      unlock()
      {
	if (!_M_owns)
	  __throw_system_error(int(errc::operation_not_permitted));
	else if (_M_device)
	  {
	    _M_device->unlock();
	    _M_owns = false;
	  }
      }

      void
      swap(unique_lock& __u) noexcept
      {
	std::swap(_M_device, __u._M_device);
	std::swap(_M_owns, __u._M_owns);
      }
---------------------------------------------------------------------------------------------------
11. release()的作用为返回mutex的指针,不会释放锁
      mutex_type*
      release() noexcept
      {
	mutex_type* __ret = _M_device;
	_M_device = 0;
	_M_owns = false;
	return __ret;
      }
---------------------------------------------------------------------------------------------------
10. owns_lock()被用来配合try_lock使用,判断是否已经上锁,来执行剩余逻辑

      bool
      owns_lock() const noexcept
      { return _M_owns; }

      explicit operator bool() const noexcept
      { return owns_lock(); }

      mutex_type*
      mutex() const noexcept
      { return _M_device; }

    private:
      mutex_type*	_M_device;
      bool		_M_owns;
    };

  /// Swap overload for unique_lock objects.
  /// @relates unique_lock
  template<typename _Mutex>
    inline void
    swap(unique_lock<_Mutex>& __x, unique_lock<_Mutex>& __y) noexcept
    { __x.swap(__y); }

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

1.2 实际使用

实际使用时,如下

  1. 没有参数
std::mutex my_mutex;
std::unique_lock<std::mutex> ul(my_mutex);  //直接上锁,lock()
  1. 有std::try_to_lock,需要用owns_lock判断是否上锁
std::mutex my_mutex;
std::unique_lock<std::mutex> ul(my_mutex, std::try_to_lock);  //直接上锁,lock()
if (ul.owns_lock())
{
	//拿到了锁
	value.push_back(i);
	//...
	//其他处理代码
}
else
{}

  1. 有std::defer_lock,需要配合unique_lock.lock()来使用
std::mutex my_mutex;
std::unique_lock<std::mutex> ul(my_mutex,std::defer_lock);  //这里_M_owns为false
ul.lock(); // _M_owns被置为true,所以才会RAII了

不可以这样

std::mutex my_mutex;
my_mutex.lock()
std::unique_lock<std::mutex> ul(my_mutex,std::defer_lock);  //这里_M_owns为false
//或者my_mutex.lock()写在后面,因为_M_owns一直为false,不会自动解锁

或者这样

std::mutex my_mutex;
std::unique_lock<std::mutex> ul(my_mutex,std::defer_lock);  //这里_M_owns为false
std::lock(ul); //因为lock参数至少为2个

2. std::lock

lock函数的传参至少有两个,通过递归的方式,完成对每一个std::mutex以及unique_lock或者shared_lock等的try lock。其实现方式与自旋类似,首先判断是否所有对象都能上锁,如果能则全锁,如果不能,则一直try lock,直到全部上锁。(第一个是unique_ptr直接lock,后续的参数为try_lock),而std::try_lock则全部为try_lock,且没有while,导致不阻塞。

当待加锁的对象中有不可用对象时

  1. std::lock会阻塞当前线程直到所有对象都可用
  2. std::try_lock不会阻塞线程当有对象不可用时会释放已经加锁的其他对象并立即返回

源码如下:

  template<typename _L1, typename _L2, typename... _L3>
    void
    lock(_L1& __l1, _L2& __l2, _L3&... __l3)
    {
      while (true)
        {
          using __try_locker = __try_lock_impl<0, sizeof...(_L3) != 0>;
          unique_lock<_L1> __first(__l1);
          int __idx;
          auto __locks = std::tie(__l2, __l3...);
          __try_locker::__do_try_lock(__locks, __idx);
          if (__idx == -1)
            {
              __first.release();
              return;
            }
        }
    }
  template<typename _Lock1, typename _Lock2, typename... _Lock3>
    int
    try_lock(_Lock1& __l1, _Lock2& __l2, _Lock3&... __l3)
    {
      int __idx;
      auto __locks = std::tie(__l1, __l2, __l3...);
      __try_lock_impl<0>::__do_try_lock(__locks, __idx);
      return __idx;
    }

c++之std::unique_lock, std::lock, std::scoped_lock及std::condition_variable_第1张图片
常见使用如下:


#include

int main()
{
std::mutex my_mutex;
std::mutex my_mutex1;
std::mutex my_mutex2;

std::unique_lock<std::mutex> ul1(my_mutex,std::defer_lock);
std::unique_lock<std::mutex> ul2(my_mutex,std::defer_lock);

//ul.lock();
std::lock(ul1,ul2,my_mutex1,my_mutex2);
}

假设有两个mutex(m1、m2),一个线程先锁住m1再锁住m2,另一个线程先锁住m2,再锁住m1,就有可能会出现死锁。std::lock内部使用了死锁避免的算法,可以有效避免死锁,如下两种情况:

std::lock(m1, m2)  //这里lock传入的是mutex
std::lock_guard lock1(m1, std::adopt_lock)  //_M_owns 为true
std::lock_guard lock2(m2, std::adopt_lock)

std::unique_lock lock1(m1, std::defer_lock);  //_M_owns 为false
std::unique_lock lock2(m2, std::defer_lock);
std::lock(lock1, lock2)  //这里lock传入的是unique_lock 

上述都是调用内部try lock,第二个不能将mutex放入lock里,即std::lock(m1, m2),
因为如果传入mutex,unique_lock< _L1> __first(__l1);后续会release掉,而release只会将_M_device =0,_M_owns =false,结果就是没有释放锁。而adopt_lock的参数设置可以帮助析构函数中释放,而无需判断(unique_lock 需要判断,但是也是true,会析构掉)

      release() noexcept
      {
	mutex_type* __ret = _M_device;
	_M_device = 0;
	_M_owns = false;
	return __ret;
      }

3.lock_guard

lock_guard更简单,利用RAII来解决可能出现的没有解锁的问题,但是其解锁功能只能通过析构函数实现,并不能如unique_lock那样,提供unlock接口,导致其并不能被std::condition_variable使用。

std::mutex my_mutex;
my_mutex.lock();
std::lock_guard<std::mutex> ul(my_mutex);

需要注意的是lock_guard< std::mutex>也是可以传第二个参数std::adopt_lock,但是与unique_lock不同的是,这里无需判断_M_owns,直接会在析构函数中解锁。实际使用场景中效果是类似的。
所以,实际使用如下:

std::mutex my_mutex;
my_mutex.lock();
std::lock_guard<std::mutex> ul(my_mutex,std::adopt_lock); //用来给my_mutex解锁

上述中std::lock_guard< std::mutex> ul(my_mutex,std::adopt_lock)中使用adopt_lock主要用来表示,在{}中的别的地方已经上锁,本lock_guard不会再上锁。而是在ul生命周期结束的时候被解锁。说白了就是用来给原来的mutex解锁用的。
所以如下写法也是可以的

std::mutex my_mutex;
std::lock_guard<std::mutex> ul(my_mutex,std::adopt_lock); 
my_mutex.lock();

2.1 源码

  template<typename _Mutex>
    class lock_guard
    {
    public:
      typedef _Mutex mutex_type;

      explicit lock_guard(mutex_type& __m) : _M_device(__m)
      { _M_device.lock(); }

      lock_guard(mutex_type& __m, adopt_lock_t) noexcept : _M_device(__m)
      { } // calling thread owns mutex

      ~lock_guard()
      { _M_device.unlock(); }

      lock_guard(const lock_guard&) = delete;
      lock_guard& operator=(const lock_guard&) = delete;

    private:
      mutex_type&  _M_device;
    };

4. std::scoped_lock

与std::lock基本一致,内部就是调用std::lock,但是多了一个析构,因为std::lock不会自动解锁,不符合RAII,所以scoped_lock中的析构函数中调用了unlock。
建议使用scoped_lock取代std::lock,用于传递多个互斥量,且与std::lock一致可以避免死锁

  template<typename... _MutexTypes>
    class scoped_lock
    {
    public:
    ---------------------------------------------------------------------------------------------------
1. 构造函数中调用std::lock
      explicit scoped_lock(_MutexTypes&... __m) : _M_devices(std::tie(__m...))
      { std::lock(__m...); }
    ---------------------------------------------------------------------------------------------------
2. 只传递互斥量,帮忙在析构函数中解锁
      explicit scoped_lock(adopt_lock_t, _MutexTypes&... __m) noexcept
      : _M_devices(std::tie(__m...))
      { } // calling thread owns mutex
    ---------------------------------------------------------------------------------------------------
3. 比std::lock多了个__m.unlock(),避免再调用unique_lock或者lock_guard
      ~scoped_lock()
      { std::apply([](auto&... __m) { (__m.unlock(), ...); }, _M_devices); }
    ---------------------------------------------------------------------------------------------------
4. 没有拷贝构造和拷贝赋值
      scoped_lock(const scoped_lock&) = delete;
      scoped_lock& operator=(const scoped_lock&) = delete;

    private:
      tuple<_MutexTypes&...> _M_devices;
    };

例子代码,这里摘抄自cpp reference

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
struct Employee {
    Employee(std::string id) : id(id) {}
    std::string id;
    std::vector<std::string> lunch_partners;
    std::mutex m;
    std::string output() const
    {
        std::string ret = "Employee " + id + " has lunch partners: ";
        for( const auto& partner : lunch_partners )
            ret += partner + " ";
        return ret;
    }
};
 
void send_mail(Employee &, Employee &)
{
    // 模拟耗时的发信操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
void assign_lunch_partner(Employee &e1, Employee &e2)
{
    static std::mutex io_mutex;
    {
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
    }
 
    {
        // 用 std::scoped_lock 取得二个锁,而无需担心
        // 其他对 assign_lunch_partner 的调用死锁我们
        // 而且它亦提供便利的 RAII 风格机制
 
        std::scoped_lock lock(e1.m, e2.m);
 
        // 等价代码 1 (用 std::lock 和 std::lock_guard )
        // std::lock(e1.m, e2.m);
        // std::lock_guard lk1(e1.m, std::adopt_lock);
        // std::lock_guard lk2(e2.m, std::adopt_lock);
 
        // 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
        // std::unique_lock lk1(e1.m, std::defer_lock);
        // std::unique_lock lk2(e2.m, std::defer_lock);
        // std::lock(lk1, lk2);
        {
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
        }
        e1.lunch_partners.push_back(e2.id);
        e2.lunch_partners.push_back(e1.id);
    }
 
    send_mail(e1, e2);
    send_mail(e2, e1);
}
 
int main()
{
    Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
 
    // 在并行线程中指派,因为就午餐指派发邮件消耗很长时间
    std::vector<std::thread> threads;
    threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
    threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
 
    for (auto &thread : threads) thread.join();
    std::cout << alice.output() << '\n'  << bob.output() << '\n'
              << christina.output() << '\n' << dave.output() << '\n';
}
alice and bob are waiting for locks
alice and bob got locks
christina and bob are waiting for locks
christina and alice are waiting for locks
dave and bob are waiting for locks
dave and bob got locks
christina and alice got locks
christina and bob got locks
Employee alice has lunch partners: bob christina 
Employee bob has lunch partners: alice dave christina 
Employee christina has lunch partners: alice bob 
Employee dave has lunch partners: bob

5. condition variable

条件变量可以参考之前写过的文章APUE——pthread_cond_wait深度分析

其实就是要注意几个api,比如wait,wait_for,重点关注的就是wait,需要配合前置的lock使用,wait流程是pred如果false,则解锁–挂起–调度走。

void wait( std::unique_lock<std::mutex>& lock );

template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );

wait在pred 为true时,不会阻塞在此,类似于

while (!pred()) {
    wait(lock);
}

所以,借助该函数可以解决虚假唤醒的问题。

这里截取一点代码,wait使用时,如果wait(_Lock& __lock, _Predicate __p)中_p未能满足条件,则继续wait
正常使用条件变量wait的步骤:

  1. 上锁
  2. 判断_p是否为true,若true则跳过3,4,否则执行3,4
  3. 解锁传入的lock
  4. 挂起线程
  5. 当有notify,被唤醒后,上锁
    template<typename _Lock>
      void
      wait(_Lock& __lock)
      {
	shared_ptr<mutex> __mutex = _M_mutex;
	unique_lock<mutex> __my_lock(*__mutex);
	_Unlock<_Lock> __unlock(__lock);             //这里定义了unlock对象,其构造函数中调用了unlock,这里解释了为何不用lock_guard
	// *__mutex must be unlocked before re-locking __lock so move
	// ownership of *__mutex lock to an object with shorter lifetime.
	unique_lock<mutex> __my_lock2(std::move(__my_lock));
	_M_cond.wait(__my_lock2);
      }


    template<typename _Predicate>
      void
      wait(unique_lock<mutex>& __lock, _Predicate __p)
      {
	while (!__p())
	  wait(__lock);
      }


    template<typename _Lock>
      struct _Unlock
      {
	explicit _Unlock(_Lock& __lk) : _M_lock(__lk) { __lk.unlock(); }

因为wait的内部实现会先解锁后上锁,同时,因为notify之前也已经上锁,所以wait会死锁,解决方法:在notify的前后加一个大括号,便于解锁

#include 
#include 
#include 
#include 
#include 
std::mutex m;

std::condition_variable cv;

void VAL_WorkThread()
{
std::unique_lock<std::mutex> lk(m);
    std::cout<<"start"<<std::endl;
       //      !!!!!!!!!!!!!!!!!!!!!
    cv.wait(lk,[]{
        std::cout<<"run"<<std::endl;
        return false;
    });/* code */
    
    std::cout<<"end"<<std::endl;

}

int main()
{
    std::cout<<"go"<<std::endl;

    std::thread work_thread(VAL_WorkThread);
    work_thread.detach();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    while (1)
    {
        { // !!!!!!!!!!!!!!!!!
        std::unique_lock<std::mutex> lg(m);
        std::cout<<"locked main"<<std::endl;

        cv.notify_one(); /* code */
        } //!!!!!!!!!!!!!!!!!!
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }
}


下面的例子是一个典型的生产者消费者问题,需要条件变量加lock解决生产队列push和pop问题

#include
#include
#include
#include
#include 
#include
#include
#include  

std::mutex my_mutex;
std::condition_variable my_cv;
int max_num = 20;
std::list<int> value;
void producer(int id)
{
    while(true)
    {
        static int i=0;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        //my_mutex.lock();
        std::unique_lock<std::mutex> ul(my_mutex,std::defer_lock);
        ul.lock();
        //std::lock_guard ul(my_mutex,std::adopt_lock);
        my_cv.wait(ul,[](){return value.size() <= max_num;});
        value.push_back(i);
        std::cout<<" push back the value "<<i<<" and thread id is "<<id<<std::endl;
        i++;
        my_cv.notify_one();
        
    }

}

void consumer(int id)
{
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::unique_lock<std::mutex> ul(my_mutex);
        my_cv.wait(ul,[](){return value.size() > 0;});
        std::cout<<"we consumed the front value "<<value.front()<<" and thread id is "<<id<<std::endl;
        value.pop_front();
        my_cv.notify_one();

    }
}

int main()
{
    std::vector<std::thread> vin;
    std::vector<std::thread> vot;
    for(int i=0; i<10; i++)
    { 
        vin.push_back(std::thread(producer,i));
        vot.push_back(std::thread(consumer,i));
    }


    for(int i=0; i<10; i++)
    { 
        vin[i].join();
        vot[i].join();
    }

    std::cout<<"end!!!"<<std::endl;
    return 0;
}
 push back the value 0 and thread id is 0
we consumed the front value 0 and thread id is 2
 push back the value 1 and thread id is 5
 push back the value 2 and thread id is 7
 push back the value 3 and thread id is 9
 push back the value 4 and thread id is 2
we consumed the front value 1 and thread id is 7
 push back the value 5 and thread id is 3
 push back the value 6 and thread id is 4
we consumed the front value 2 and thread id is 9
we consumed the front value 3 and thread id is 5
we consumed the front value 4 and thread id is 4
 push back the value 7 and thread id is 1
 push back the value 8 and thread id is 6
 push back the value 9 and thread id is 8
we consumed the front value 5 and thread id is 0
we consumed the front value 6 and thread id is 1
we consumed the front value 7 and thread id is 8
we consumed the front value 8 and thread id is 6
we consumed the front value 9 and thread id is 3
 push back the value 10 and thread id is 0
 push back the value 11 and thread id is 8
 push back the value 12 and thread id is 5
 push back the value 13 and thread id is 3
 push back the value 14 and thread id is 1
we consumed the front value 10 and thread id is 6
we consumed the front value 11 and thread id is 2
we consumed the front value 12 and thread id is 7
we consumed the front value 13 and thread id is 1

再来一个c++ concurrency in action里的源码,这里想表达为何4用了unique_lock,因为其比较灵活,可以随时解锁。它还可以用于待处理但还未处理的数据6。处理数据可能是一个耗时的操作,不适合长期占用锁。

std::mutex mut;
std::queue<data_chunk> data_queue;  // 1
std::condition_variable data_cond;
void data_preparation_thread()
{
  while(more_data_to_prepare())
  {
    data_chunk const data=prepare_data();
    std::lock_guard<std::mutex> lk(mut);
    data_queue.push(data);  // 2
    data_cond.notify_one();  // 3
  }
}
void data_processing_thread()
{
  while(true)
  {
    std::unique_lock<std::mutex> lk(mut);  // 4
    data_cond.wait(
         lk,[]{return !data_queue.empty();});  // 5
    data_chunk data=data_queue.front();
    data_queue.pop();
    lk.unlock();  // 6
    process(data);
    if(is_last_chunk(data))
      break;
  }
}

6. shared_mutex

c++17加入的shared_mutex以及之前的std::shared_timed_mutex可以很好的解决读写锁问题。

对于更新操作,可以使用std::lock_guard< std::shared_mutex>和std::unique_lock< std::shared_mutex>上锁,对于共享读操作,std::shared_lock< std::shared_mutex>获取访问权。
这里引用c++ concurrency in action里的源码

#include 
#include 
#include 
#include 
class dns_entry;
class dns_cache
{
  std::map<std::string,dns_entry> entries;
  mutable std::shared_mutex entry_mutex;
public:
  dns_entry find_entry(std::string const& domain) const
  {
    std::shared_lock<std::shared_mutex> lk(entry_mutex);  // 1
    std::map<std::string,dns_entry>::const_iterator const it=
       entries.find(domain);
    return (it==entries.end())?dns_entry():it->second;
  }
  void update_or_add_entry(std::string const& domain,
                           dns_entry const& dns_details)
  {
    std::lock_guard<std::shared_mutex> lk(entry_mutex);  // 2
    entries[domain]=dns_details;
  }
};

find_entry()使用std::shared_lock<>来保护共享和只读权限①;这就使得多线程可以同时调用find_entry(),且不会出错。另一方面,update_or_add_entry()使用std::lock_guard<>实例,当表格需要更新时②,为其提供独占访问权限;update_or_add_entry()函数调用时,独占锁会阻止其他线程对数据结构进行修改,并且阻止线程调用find_entry()。

cpp_reference_std::scoped_lock
cpp_reference_std::unique_lock
scope_lock与lock_guard区别
std::lock_guard or std::scoped_lock
C++多线程unique_lock详解
C++多线程——读写锁shared_lock/shared_mutex
shared_lock/shared_mutex看看可以直接参考上面文章

你可能感兴趣的:(c++学习,c++,开发语言,后端)