===

转转于:gitcode.csdn.net/65eec6571a8…

C 11 lock_guard 和 unique_lock 简介,运用以及其相应的原理完结

文章目录

前语

最近在写C 程序,需求用到 lock_guard ,并记录 C 11新特性 lock_guard 和 unique_lock 的运用。

一、简介

1.1 lock_guard

在 C 11 中,为了便利完结主动加锁和解锁的操作,供给了 lock_guard 类模板。它是一个轻量级的 RAII(资源获取即初始化)类,用于在效果域完毕时主动开释互斥锁。

std::lock_guard 用于办理互斥锁的加锁和解锁操作。它的首要效果是在结构函数中获取一个互斥锁,然后在析构函数中主动开释该锁,以确保在锁维护区域的完毕时正确解锁。

std::lock_guard 的效果是获取互斥量的锁,并在效果域完毕时主动开释锁。这样能够防止手动办理锁的杂乱性和危险,一起也能够确保在运用同享资源时不会被其他线程打断。

简略来说便是运用 std::lock_guard 让开发者运用时不必关怀 std::mutex 锁的开释。

#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx; // the mutex to protect the shared resource
void thread_function()
{
    std::lock_guard<std::mutex> lock(mtx); // acquire the lock
    std::cout << "Thread " << std::this_thread::get_id() << " is accessing the shared resource." << std::endl;
    // access the shared resource here...
	//other code
}
int main()
{
    std::thread t1(thread_function);
    std::thread t2(thread_function);
    t1.join();
    t2.join();
    return 0;
}

上述的std::lock_guard在 履行完 thread_function 函数时,才主动开释该锁,但是咱们只需求维护咱们的数据,当咱们运用 std::lock_guard 时,它的效果域应该被约束在需求维护的临界区域内。这样能够确保锁在不需求的时候及时开释,防止死锁和其他并发问题。因而咱们在运用 std::lock_guard 时能够 约束其效果域:

#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx; // the mutex to protect the shared resource
void thread_function()
{
	//拜访临界区域资源
	{
	    std::lock_guard<std::mutex> lock(mtx); // 在结构函数中调用 lock()
	    std::cout << "Thread " << std::this_thread::get_id() << " is accessing the shared resource." << std::endl;
	    // access the shared resource here...
	}
	//脱离效果域,lock_guard会调用其析构函数,会主动调用 unlock()
	//other code
}
int main()
{
    std::thread t1(thread_function);
    std::thread t2(thread_function);
    t1.join();
    t2.join();
    return 0;
}

变量在脱离其效果域时被毁掉,会主动调用其析构函数。

其他一个简略例子:

#include <mutex>
#include <iostream>
#include <thread>
class Counter {
public:
    Counter() : count_(0) {}
    void increment() {
        std::lock_guard<std::mutex> lock(mtx_);
          count_;
    }
    void decrement() {
        std::lock_guard<std::mutex> lock(mtx_);
        --count_;
    }
    int value() const {
        std::lock_guard<std::mutex> lock(mtx_);
        return count_;
    }
private:
    int count_;
    mutable std::mutex mtx_;
};
void worker(Counter& counter) {
    for (int i = 0; i < 100000;   i) {
        counter.increment();
        counter.decrement();
    }
}
int main() {
    Counter counter;
    std::thread t1(worker, std::ref(counter));
    std::thread t2(worker, std::ref(counter));
    t1.join();
    t2.join();
    std::cout << "Final count: " << counter.value() << std::endl;
    return 0;
}

在这个例程中,咱们界说了一个名为 Counter 的类,它包括一个计数器和一个互斥量。计数器的 increment、decrement 和 value 操作都运用了 std::lock_guard 来维护计数器的拜访,然后防止了数据竞赛等并发问题。

在 increment、decrement 和 value 操作中,咱们运用 std::lock_guard 目标 lock 来确认互斥量 mtx_。在确认互斥量后,咱们能够安全地拜访计数器,并在操作完结后主动开释互斥量。

在 worker 函数中,咱们运用两个线程来模仿计数器的增减进程。在每次循环中,线程会调用 increment 和 decrement 操作各一次,然后使计数器的值不变。最终,咱们运用 value 操作输出计数器的最终值。

1.2 RAII

RAII(Resource Acquisition Is Initialization)是 C 中一种重要的编程技能,它的核心思维是将资源的获取和开释与目标的结构和析构绑定在一起,以确保在目标结构时获取资源,在目标析构时开释资源,然后防止资源走漏和过错。

RAII 技能的基本原则是:将资源的获取和开释的代码封装在一个类的结构函数和析构函数中。在目标结构时,资源被获取并初始化;在目标析构时,资源被开释。这样,无论是正常退出仍是反常退出,都能够确保资源被正确地开释,防止了资源走漏和过错。

1.3 原理

lock_guard 是一个类模板,源码如下:

  /** @brief A simple scoped lock type.
   *
   * A lock_guard controls mutex ownership within a scope, releasing
   * ownership in the destructor.
   */
  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;
    };

(1)
std::lock_guard是 C 11 引进的一个规范库类模板,界说在 头文件中。它的模板参数 Mutex 是一个互斥锁类型,能够是 `std::mutex、std::recursive_mutex、std::timed_mutex、std::recursive_timed_mutex等类型的互斥锁。

  template<typename _Mutex>
  class lock_guard
    {
    public:
      typedef _Mutex mutex_type;
    private:
      mutex_type&  _M_device;
    }

std::lock_guard 运用模板类完结,是因为它需求支撑不同类型的互斥锁目标,而不同类型的互斥锁目标有不同的完结办法和接口,因而需求运用模板类来完结 std::lock_guard。

在 C 11 中,规范库中供给了多种类型的互斥锁目标,如 std::mutex、std::recursive_mutex、std::timed_mutex、std::recursive_timed_mutex 等,这些互斥锁目标都有不同的完结办法和接口,但它们都具有 lock() 和 unlock() 办法,用于确认和解锁互斥锁目标。因而,为了支撑这些不同类型的互斥锁目标,需求运用模板类来完结 std::lock_guard,以便在结构函数和析构函数中调用 lock() 和 unlock() 办法,然后完结主动确认和解锁互斥锁目标的功用。

运用模板类完结 std::lock_guard 还有一个好处,便是能够防止在运用时进行类型转化。因为模板类能够根据传入的参数类型主动推导出模板参数类型,然后在编译时确认模板类的详细实例化类型。这样能够防止手动进行类型转化,进步代码可读性和安全性。

总归,运用模板类完结 std::lock_guard 是为了支撑不同类型的互斥锁目标,而且防止在运用时进行类型转化,进步代码的可读性和安全性。

(2)
std::lock_guard的结构函数接受一个互斥锁的引证,并主动获取锁。假如互斥锁现已被其他线程确认,则当时线程会被堵塞,直到锁可用停止。假如获取锁时产生反常,则会当即开释锁。

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

其间explicit 是一个要害字,用于修饰结构函数,它的效果是制止隐式转化。假如一个结构函数被声明为 explicit,那么它就不能被用于隐式转化,只能被用于显式结构目标。

在 std::lock_guard 中,结构函数被声明为 explicit,这是因为 std::lock_guard 的效果是获取互斥量的锁,在结构函数中调用互斥量的 lock 成员函数获取锁。假如结构函数不加 explicit 要害字,那么在创立 std::lock_guard 目标时,能够隐式地将互斥量目标作为参数传递,这会导致在创立目标时就获取了锁,然后影响程序的正确性和功能。

(3)
std::lock_guard 的析构函数会在目标生命周期完毕时主动开释锁。这意味着,在锁维护区域完毕时,无论是正常完毕仍是反常完毕,都会主动开释锁,然后防止了忘掉开释锁的问题。

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

(4)
需求留意的是,std::lock_guard是一个非复制结构和非复制赋值结构的类,这意味着它不能被复制或赋值。这是因为复制或赋值会导致多个 std::lock_guard目标一起办理同一个互斥锁,然后或许导致死锁或其他并发问题。假如需求在多个线程之间同享锁,则应该运用 std::shared_lock。

//复制结构函数
lock_guard(const lock_guard&) = delete;
//复制赋值结构函数
lock_guard& operator=(const lock_guard&) = delete;

delete 是 C 11 中的一个要害字,它的效果是制止某个函数的运用。在类中运用 delete 的语法,能够制止复制结构函数和赋值运算符的运用,这样能够防止类目标被复制或赋值,防止了程序犯错的危险。

在 std::lock_guard 中,咱们运用了 delete 要害字来制止复制结构函数和赋值运算符的运用。这是因为 std::lock_guard 的效果是获取互斥量的锁,它是一个非复制的类型,不能被复制结构或赋值。

在 std::lock_guard 中,复制结构函数和赋值运算符也被删除了,这样就能够防止在运用 std::lock_guard 目标时进行复制或赋值,确保了程序的正确性和功能。

制止复制和复制赋值操作的原因是,lock_guard 目标的所有权是唯一的,不能被多个目标同享,不然会导致死锁等问题。因而,为了防止复制和复制赋值操作的过错运用,C 11 中将 lock_guard 的复制结构函数和复制赋值运算符设置为 delete,使其不能被调用。

delete 要害字是 C 11 中的一个重要特性,它能够协助咱们制止某些函数的运用,然后防止程序犯错和进步代码的强健性和可维护性。在 std::lock_guard 中,运用 delete 要害字能够制止复制结构函数和赋值运算符的运用,然后防止了类目标被复制或赋值的过错,进步了程序的正确性和功能。

二、unique_lock

2.1 简介

std::unique_lock 是 C 11 中的一个互斥量确认器,它供给了灵敏的确认和解锁办法,支撑超时等候和可中断等特性。与 std::lock_guard 不同,std::unique_lock 能够在结构函数中挑选是否确认,以及在析构函数中挑选是否解锁,然后供给了更多的操控和灵敏性。

unique_lock操控效果域内的互斥体所有权。互斥体的所有权能够延迟到结构之后,而且能够经过移动结构或移动赋值(move construction or move assignment)将其搬运到另一个unique_lock。假如析构函数运行时具有互斥锁,则所有权将被开释。

2.2 运用

#include <queue>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <thread>
template <typename T>
class ThreadSafeQueue {
public:
    ThreadSafeQueue() {}
    void push(const T& value) {
        std::unique_lock<std::mutex> lock(mtx_);
        queue_.push(value);
        cv_.notify_one();
    }
    void pop(T& value) {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]() { return !queue_.empty(); });
        value = queue_.front();
        queue_.pop();
    }
private:
    std::queue<T> queue_;
    std::mutex mtx_;
    std::condition_variable cv_;
};
void producer(ThreadSafeQueue<int>& queue) {
    for (int i = 0; i < 10;   i) {
        queue.push(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
void consumer(ThreadSafeQueue<int>& queue) {
    int value;
    for (int i = 0; i < 10;   i) {
        queue.pop(value);
        std::cout <<"Consumer " << std::this_thread::get_id() << " popped " << value << std::endl;
    }
}
int main() {
    ThreadSafeQueue<int> queue;
    std::thread t1(producer, std::ref(queue));
    std::thread t2(consumer, std::ref(queue));
    std::thread t3(consumer, std::ref(queue));
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

在这个例程中,咱们界说了一个名为 ThreadSafeQueue 的模板类,它运用 std::queue、std::mutex 和 std::condition_variable 来完结线程安全的行列。行列的 push 和 pop 操作都运用了 std::unique_lock 来维护同享资源的拜访,然后防止了数据竞赛等并发问题。

在 push 操作中,咱们首要创立了一个 std::unique_lock 目标 lock,然后运用 lock 目标确认互斥量 mtx_。在确认互斥量后,咱们向行列中增加一个元素,并运用条件变量 cv_ 告诉等候的线程。在 pop操作中,咱们也首要创立了一个 std::unique_lock 目标 lock,然后运用 lock 目标确认互斥量 mtx_。接着,咱们运用条件变量 cv_ 等候行列中有元素可供消费,假如行列为空,则线程会被堵塞,直到行列中有元素可供消费停止。一旦等候的条件得到满意,咱们从行列中取出一个元素,并将其赋值给 value 参数,然后将这个元素从行列中弹出。

在 main 函数中,咱们创立了一个 ThreadSafeQueue 目标 queue,并运用两个顾客线程和一个出产者线程来模仿行列的出产和消费进程。在每次循环中,出产者线程向行列中增加一个元素,并休眠 100 毫秒。顾客线程则从行列中取出元素,并输出取出的元素信息。

这个例程展示了怎么运用 std::unique_lock 来维护同享资源的拜访,完结线程安全的行列。经过运用 std::unique_lock,咱们能够愈加灵敏地操控互斥量的拜访,防止了数据竞赛等并发问题。一起,这个例程还展示了怎么运用条件变量来完结线程之间的同步,然后完结愈加高效和强健的多线程程序。

其间:

 void push(const T& value) {
     std::unique_lock<std::mutex> lock(mtx_);
     queue_.push(value);
     cv_.notify_one();
 }

这段代码完结了一个线程安全的行列中的 push 操作,它的详细含义如下:

(1)创立一个 std::unique_lock 目标 lock,并运用 mtx_ 互斥量对其进行确认。
(2)向行列 queue_ 中增加一个元素 value。
(3)运用条件变量 cv_ 的 notify_one 函数告诉等候的线程。
(4)在 push 操作完结后,std::unique_lock 目标 lock 会主动开释互斥量 mtx_。

这个 push 操作的意图是向行列中增加一个元素,并告诉等候的线程。因为行列是同享资源,多个线程或许一起测验向行列中增加元素,因而咱们需求运用互斥量 mtx_ 对其进行维护,防止数据竞赛等并发问题。当一个线程调用 push 操作时,它会创立一个 std::unique_lock 目标 lock,并运用互斥量 mtx_ 将其确认。这样,其他线程就无法一起拜访行列,然后确保了线程安全。

在向行列中增加元素后,咱们运用条件变量 cv_ 的 notify_one 函数告诉等候的线程。这样,假如有线程在等候行列中有元素可供消费,它就会被唤醒,并测验获取互斥量 mtx_,然后持续履行消费操作。

最终,当 push 操作完结后,std::unique_lock 目标 lock 会主动开释互斥量 mtx_。这样,其他线程就能够持续拜访行列,然后完结了线程安全的行列。

其间:

 void pop(T& value) {
     std::unique_lock<std::mutex> lock(mtx_);
     cv_.wait(lock, [this]() { return !queue_.empty(); });
     value = queue_.front();
     queue_.pop();
 }

这段代码完结了一个线程安全的行列中的 pop 操作,它的详细含义如下:

(1)创立一个 std::unique_lock 目标 lock,并运用 mtx_ 互斥量对其进行确认。
(2)运用条件变量 cv_ 的 wait 函数等候行列中有元素可供消费。在等候期间,假如行列为空,则线程会被堵塞。
(3)一旦等候的条件得到满意(即行列中有元素可供消费),则从行列 queue_ 中取出一个元素,并将其赋值给 value 参数。
(4)将这个元素从行列中弹出。
(5)在 pop 操作完结后,std::unique_lock 目标 lock 会主动开释互斥量 mtx_。

这个 pop 操作的意图是从行列中取出一个元素。因为行列是同享资源,多个线程或许一起测验从行列中取出元素,因而咱们需求运用互斥量 mtx_ 对其进行维护,防止数据竞赛等并发问题。当一个线程调用 pop 操作时,它会创立一个 std::unique_lock 目标 lock,并运用互斥量 mtx_ 将其确认。这样,其他线程就无法一起拜访行列,然后确保了线程安全。

在确认互斥量后,咱们运用条件变量 cv_ 的 wait 函数等候行列中有元素可供消费。在等候期间,假如行列为空,则线程会被堵塞,等候其他线程向行列中增加元素并告诉等候的线程。假如行列不为空,则线程会持续履行后续操作。

补白:std::unique_lock供给了一种便利的办法来完结条件变量的同步操作,即经过 std::condition_variable 的 wait() 办法来开释锁并等候条件变量满意,然后从头获取锁并持续履行。这种办法一般用于需求等候某个条件变量满意的情况,能够防止在等候进程中一向占用锁资源,然后进步了并发功能。

一旦等候的条件得到满意,即行列中有元素可供消费,咱们从行列中取出一个元素,并将其赋值给 value 参数。这样,顾客线程就能够获取行列中的元素,并进行后续的处理操作。

最终,咱们将这个元素从行列中弹出,并在 pop 操作完结后,std::unique_lock 目标 lock 会主动开释互斥量 mtx_。这样,其他线程就能够持续拜访行列,然后完结了线程安全的行列。

关于:

 std::unique_lock<std::mutex> lock(mtx_);
 cv_.wait(lock, [this]() { return !queue_.empty(); });

这段代码是运用条件变量 cv_ 等候行列 queue_ 不为空的操作:

(1)等候前先获取一个 std::unique_lock std::mutex 类型的锁 lock,该锁与互斥量 mutex_ 相相关,用于确保在等候条件变量时对行列 queue_ 的拜访是线程安全的。

(2)调用条件变量 cv_ 的 wait() 办法,该办法会开释锁 lock 并等候条件变量满意,此时线程会处于堵塞状况。

(3)在等候进程中,条件变量 cv_ 会主动开释锁 lock,以便其他线程能够对行列 queue_ 进行拜访,然后进步并发功能。

(4)等候条件变量满意后,线程会从头获取锁 lock,并持续履行后续操作。

(5)等候条件变量的满意条件是经过一个 Lambda 表达式 this { return !queue_.empty(); } 来指定的。该 Lambda 表达式的效果是查看行列 queue_ 是否为空,假如不为空则回来 true,表明等候条件变量的满意条件现已满意,不然回来 false,表明持续等候条件变量的满意。

需求留意的是,Lambda 表达式中运用了成员变量 queue_ 和 this 指针,这是因为它是在类的成员函数中运用的,经过运用 this 指针能够拜访到类的成员变量和办法。

总归,这段代码的效果是完结了一种等候行列不为空的同步操作,经过条件变量 cv_ 来完结等候和唤醒线程的操作,然后防止了忙等的情况,进步了程序的功率

2.3 原理

std::unique_lock 是 C 11 引进的一种互斥锁封装,它供给了一种愈加灵敏的办法来办理锁的生命周期和操作。

在运用 std::unique_lock 时,需求传入一个互斥锁目标,它能够是 std::mutex、std::recursive_mutex 或其他支撑 Lockable 概念的目标。std::unique_lock 目标会办理这个锁目标的确认和开释,而且供给了许多办法来操控锁的行为。

std::unique_lock 的原理是根据 RAII(Resource Acquisition Is Initialization) 规划形式来完结的。当一个 std::unique_lock 目标被创立时,它会在结构函数中测验确认与之相关的互斥锁目标。当 std::unique_lock 目标被毁掉时,它会在析构函数中主动开释它所持有的锁。这种主动化的锁的办理办法,能够防止在代码中手动办理锁的生命周期,然后减少了犯错的危险。

此外,std::unique_lock 还供给了一些操控锁行为的办法,例如:

(1)lock():手动确认互斥锁目标;
(2)unlock():手动开释互斥锁目标;
(3)try_lock():测验确认互斥锁目标,假如锁现已被其他线程持有,则回来 false。

经过这些办法,能够愈加灵敏地操控锁的行为,然后完结愈加杂乱的同步操作。

总归,std::unique_lock 的原理是根据 RAII 规划形式和互斥锁目标的确认和开释机制来完结的,它供给了一种愈加灵敏和安全的办法来办理锁的生命周期和操作。

  template<typename _Mutex>
    class unique_lock
    {
    public:
      typedef _Mutex mutex_type;
      unique_lock() noexcept
      : _M_device(0), _M_owns(false)
      { }
      explicit unique_lock(mutex_type& __m)
      : _M_device(std::__addressof(__m)), _M_owns(false)
      {
	lock();
	_M_owns = true;
      }
      ~unique_lock()
      {
	if (_M_owns)
	  unlock();
      }	
      unique_lock(const unique_lock&) = delete;
      unique_lock& operator=(const unique_lock&) = delete;
      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;
      }
    private:
      mutex_type*	_M_device;
      bool		_M_owns;
    };

其间 _M_owns 是 unique_lock 类的成员变量,表明当时 unique_lock 目标是否具有互斥锁目标的所有权。

(1)

      explicit unique_lock(mutex_type& __m)
      : _M_device(std::__addressof(__m)), _M_owns(false)
      {
	lock();
	_M_owns = true;
      }

这段代码是 std::unique_lock 的结构函数的完结代码,用于创立一个 unique_lock 目标并确认给定的互斥锁目标。其间:

explicit 要害字表明该结构函数是显式结构函数,只能用于显式地创立 unique_lock 目标。
mutex_type& __m 表明传入的互斥锁目标的引证。
_M_device(std::__addressof(__m)) 表明将传入的互斥锁目标的地址存储在 _M_device 成员变量中,
std::__addressof 是一个协助函数,用于获取目标的地址。
lock() 是 unique_lock 类的成员函数,用于确认互斥锁目标。
_M_owns(false) 表明初始化 _M_owns 成员变量为 false,表明当时 unique_lock 目标未具有互斥锁目标的所有权。
lock() 成功后,将 _M_owns 成员变量设置为 true,表明当时 unique_lock 目标具有互斥锁目标的所有权。

(2)

     ~unique_lock()
     {
if (_M_owns)
  unlock();
     }

假如当时 unique_lock 目标具有互斥锁目标的所有权(即 _M_owns 为 true),则在 unique_lock 目标被毁掉时主动履行解锁操作,将互斥锁目标解锁。这是 RAII 技能的应用,确保在脱离 unique_lock 目标的效果域时主动解锁互斥锁目标,防止了忘掉手动解锁的过错。

(3)

      unique_lock(const unique_lock&) = delete;
      unique_lock& operator=(const unique_lock&) = delete;
      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;
      }

这段代码是 std::unique_lock 的移动结构函数和移动赋值运算符的完结代码,用于完结 unique_lock 目标的移动语义,行将一个 unique_lock 目标的所有权搬运给另一个 unique_lock 目标,而不是进行复制和复制赋值操作。

制止复制和复制赋值操作的原因是,unique_lock 目标的所有权是唯一的,不能被多个目标同享,不然会导致死锁等问题。因而,为了防止复制和复制赋值操作的过错运用,C 11 中将 unique_lock 的复制结构函数和复制赋值运算符设置为delete,使其不能被调用。假如需求将一个 unique_lock 目标传递给另一个函数或目标,能够运用移动结构函数和移动赋值运算符来搬运所有权,而不是复制或复制赋值操作。

unique_lock(unique_lock&& __u) noexcept 表明移动结构函数,用于将一个右值引证的 unique_lock 目标的所有权搬运给新创立的 unique_lock 目标。详细来说,这个移动结构函数的完结是:

__u 是一个右值引证的 unique_lock 目标,表明要移动的目标。
noexcept 要害字表明该函数不会抛出反常。
_M_device(__u._M_device) 表明即将移动的目标的 _M_device 成员变量存储在新创立的目标的 _M_device 成员变量中,完结了所有权的搬运。
_M_owns(__u._M_owns) 表明即将移动的目标的 _M_owns 成员变量存储在新创立的目标的 _M_owns 成员变量中,完结了所有权的搬运。
__u._M_device = 0; 和 __u._M_owns = false; 表明即将移动的目标的 _M_device 和 _M_owns 成员变量设置为默认值,以防止在移动完结后履行解锁操作。

unique_lock& operator=(unique_lock&& __u) noexcept 表明移动赋值运算符,用于将一个右值引证的 unique_lock 目标的所有权搬运给当时的 unique_lock 目标。详细来说,这个移动赋值运算符的完结是:

__u 是一个右值引证的 unique_lock 目标,表明要移动的目标。
if(_M_owns) unlock(); 表明假如当时 unique_lock 目标具有互斥锁目标的所有权,则履行解锁操作,防止在移动完结后呈现死锁的情况。
unique_lock(std::move(__u)).swap(*this); 表明创立一个临时的 unique_lock 目标,即将移动的目标的所有权搬运给该目标,然后经过 swap() 办法将该目标的所有权搬运给当时的 unique_lock 目标,完结了所有权的搬运。
__u._M_device = 0; 和 __u._M_owns = false; 表明即将移动的目标的 _M_device 和 _M_owns 成员变量设置为默认值,以防止在移动完结后履行解锁操作。
return *this; 表明回来当时的 unique_lock 目标的引证,用于支撑链式调用。

这段代码的意思是,为完结 unique_lock 目标的移动语义,制止复制和复制赋值操作,创立了移动结构函数和移动赋值运算符。移动结构函数将一个右值引证的 unique_lock 目标的所有权搬运给新创立的目标,移动赋值运算符将一个右值引证的 unique_lock 目标的所有权搬运给当时的 unique_lock 目标。经过移动语义,能够防止进行复制和复制赋值操作,进步程序的功能。

三、 lock_guard 和 unique_lock比较

std::lock_guard 和 std::unique_lock 都是用于办理互斥锁的 C 11 规范库类,它们的首要区别在于锁的办理办法和灵敏性。

3.1 锁的办理办法

std::lock_guard 是一种简略的锁办理器,它的效果是在结构函数中主动确认互斥锁目标,并在析构函数中主动开释锁。因为 std::lock_guard 的确认和开释是在结构函数和析构函数中完结的,因而它遵循 RAII 规划形式,能够防止在代码中手动办理锁的生命周期。

std::unique_lock 也是一种锁办理器,但比较 std::lock_guard,它供给了愈加灵敏的确认和开释办法。std::unique_lock 的结构函数能够接受一个 std::defer_lock 参数,用于创立一个未确认的 std::unique_lock 目标,而确认操作则需求手动调用 lock() 办法来完结。此外,std::unique_lock 还供给了一些其他的特性,例如:

(1)能够在结构函数中传入一个 std::adopt_lock_t 参数,用于接收现已确认的互斥锁目标的所有权。
(2)能够随时手动开释锁,并在需求时从头获取锁。
(3)能够经过 try_lock() 办法测验确认互斥锁目标,假如锁现已被其他线程持有,则回来 false。

3.2 灵敏性

因为 std::unique_lock 供给了愈加灵敏的确认和开释办法,因而它比 std::lock_guard 愈加灵敏和适用于需求进行杂乱同步操作的情况。例如,在需求等候某个条件变量满意时,能够运用 std::unique_lock 结合条件变量来完结等候操作,然后防止了忙等的情况,进步了程序的功率。

此外,因为 std::unique_lock 供给了愈加灵敏的确认和开释办法,因而它的功能或许会比 std::lock_guard 稍微差一些。假如只需求一个简略的锁办理器来维护同享资源,而不需求进行杂乱的同步操作,那么运用 std::lock_guard 或许愈加适宜。

总归,std::lock_guard 和 std::unique_lock 都是用于办理互斥锁的 C 11 规范库类,它们的首要区别在于锁的办理办法和灵敏性。在需求进行杂乱同步操作时,建议运用 std::unique_lock。在只需求一个简略的锁办理器时,能够运用 std::lock_guard 来简化代码。

3.3 可移动性

可移动性:std::unique_lock 目标是可移动的,而 std::lock_guard 则不是。这意味着,能够运用 std::unique_lock 目标进行移动语义,然后防止了多余的互斥量确认和解锁操作。而 std::lock_guard 则不支撑移动语义,只能运用复制语义,这样或许会导致不必要的互斥量确认和解锁操作。

C 11 规范中,std::lock_guard 并没有完结移动结构函数和移动赋值运算符。因为 std::lock_guard 的效果是在结构函数中主动确认互斥锁目标,在析构函数中主动开释锁,因而它的完结办法不适合支撑移动语义。假如支撑移动语义,就或许导致移动后本来的目标不再具有确认互斥锁的才能,或许移动后两个目标都具有确认同一个互斥锁的才能,这都会导致不安全的多线程行为。

假如需求支撑移动语义,能够运用 std::unique_lock。std::unique_lock 供给了愈加灵敏的确认和开释办法,并支撑移动语义。能够运用 std::move() 函数将一个 std::unique_lock 目标移动到另一个目标中,如下所示:

std::mutex mutex;
std::unique_lock<std::mutex> lock1(mutex);
std::unique_lock<std::mutex> lock2(std::move(lock1)); // 移动 lock1 到 lock2

需求留意的是,在运用移动语义时,需求确保本来的 std::unique_lock 目标不再运用互斥锁目标,不然或许会导致不确认的行为。因为 std::unique_lock 目标能够手动开释和从头获取锁,因而在运用移动语义时需求特别留意锁的状况和所有权的搬运。

总结

以上例程参阅于 Chatgpt ,Chatgpt有一些的描绘会有一些过错,最好实践例程,并参阅源码进行一定的比照。