【C++入门到精通】互斥锁 (Mutex) C++11 [ C++入门 ]

简介: 【C++入门到精通】互斥锁 (Mutex) C++11 [ C++入门 ]

引言

在多线程编程中,保证数据的同步和互斥是至关重要的。而互斥锁(Mutex)作为一种常用的同步机制,在C++11标准中被引入,提供了一种简单有效的方式来控制多个线程对共享资源的访问。互斥锁可以确保同一时间只有一个线程可以持有锁,并且其他线程需要等待锁释放后才能继续执行,从而避免了多个线程同时访问共享资源所导致的数据竞争和不一致性问题。本文将详细介绍互斥锁的种类、使用方法以及一些常见的注意事项,帮助读者更好地理解和应用互斥锁来实现线程安全的程序。

一、Mutex的简介

⭕Mutex官方文档

Mutex(互斥量)是一种同步原语,用于实现多线程环境下的资源互斥访问。它允许多个线程同时访问共享资源,但在任何给定时间只能有一个线程能够获得对该资源的独占访问权。Mutex主要用于防止数据竞争和确保数据的一致性。

在C++11之前,开发人员通常使用操作系统提供的互斥机制来实现线程间的同步。而C++11引入的Mutex则提供了一种标准化的、跨平台的解决方案,使得多线程编程更加简单和可靠。

Mutex的基本操作包括锁定(lock)和解锁(unlock)。当一个线程需要访问共享资源时,它会尝试对Mutex进行加锁操作,如果Mutex已经被其他线程锁定,那么该线程将被阻塞,直到Mutex被解锁。一旦线程完成对共享资源的操作,它会释放Mutex,允许其他线程获得对资源的访问权。

使用Mutex可以有效地避免数据竞争和保护共享资源的一致性。然而,Mutex也存在一些潜在问题,如死锁(deadlock)和饥饿(starvation)。为了避免这些问题,开发人员需要仔细设计和管理Mutex的使用,并采用合适的同步机制。

二、Mutex的种类

⭕在C++11中,Mutex总共包了四个互斥量的种类分别是:

Mutex类型

描述

std::mutex

最基本的互斥锁类型,用于实现线程间的互斥访问。只允许一个线程获得锁,其他线程需要等待锁被释放才能继续执行。

std::recursive_mutex

与std::mutex类似,但允许同一线程多次获取锁。也就是说,同一线程可以多次对该锁进行加锁操作,每次加锁都需要对应的解锁操作。

std::timed_mutex

可限时等待的互斥锁类型。与std::mutex类似,但允许线程在尝试获取锁时设置一个超时时间。如果锁在指定的时间内无法被获得,线程将不再等待并返回相应的错误代码。

std::recursive_timed_mutex

可限时等待的递归互斥锁类型。结合了std::recursive_mutex和std::timed_mutex的特性,允许同一线程多次获取锁,并且可以设置超时时间。

以上是四种常见的Mutex类型及其描述,适用于不同的场景,下面我会详细介绍这四个Mutex类型。

1. std::mutex (基本互斥锁)

std::mutex是C++标准库中提供的最基本的互斥锁类型之一。它用于实现线程间的互斥访问,即在一个时间点只允许一个线程获得锁,其他线程需要等待锁被释放才能继续执行。使用std::mutex可以保证多个线程对共享资源的访问顺序,并避免数据竞争产生的问题。

🚨注意该类的对象之间不能拷贝,也不能进行移动

std::mutex最常用的三个函数是

函数名 描述
lock() 尝试获取互斥锁。如果未被其他线程占用,则当前线程获取锁;否则阻塞等待锁的释放。
unlock() 释放互斥锁。如果当前线程持有锁,则释放锁;否则行为未定义。
try_lock() 尝试获取互斥锁,不会阻塞线程。如果未被其他线程占用,则当前线程获取锁并返回true;否则返回false。


这三个函数组成了基本的互斥锁操作,也是使用std::mutex时最常用的三个函数。其中,lock()和unlock()通常需要成对使用,以确保锁得到正确的管理。try_lock()则可以用于一些特殊情况下的非阻塞式加锁操作,例如在轮询等待某个资源时,可以尝试获取锁并立即返回结果。

🚨注意事项

  1. 线程函数调用lock()时,可能会发生以下三种情况:
  • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
  • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

线程函数调用try_lock()时,可能会发生以下三种情况:

  • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock释放互斥量。
  • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

2. std::recursive_mutex (递归互斥锁)

std::recursive_mutex是C++标准库中提供的一个递归互斥锁类型,用于实现线程间的互斥访问。与std::mutex相比,std::recursive_mutex可以允许同一线程多次获取互斥锁,而不会导致死锁。简单来说就是允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock()

std::recursive_mutex定义在<mutex>头文件中。与std::mutex类似,可以通过定义

std::recursive_mutex对象来创建一个递归互斥锁。例如:

#include <mutex>
//这里定义了一个名为mtx的std::recursive_mutex对象,用于保护某个共享资源的访问。
std::recursive_mutex mtx;

std::recursive_mutex的主要方法和std::mutex相同,包括lock()、unlock()和try_lock()。这些方法的功能和使用方式也与std::mutex一致。区别在于,当同一线程多次尝试获取std::recursive_mutex时,它不会导致死锁,而是允许同一线程多次获取锁,需要相应次数的解锁操作才能完全释放锁。

3. std::timed_mutex (限时等待互斥锁)

std::timed_mutex是C++标准库中提供的一个可超时等待的互斥锁类型,用于实现线程间的互斥访问。与std::mutex相比,std::timed_mutex在尝试获取锁的时候可以设置超时时间,避免线程由于无法获取锁而一直被阻塞等待,从而提高程序的健壮性。

std::timed_mutex定义在<mutex>头文件中。与std::mutex类似,可以通过定义std::timed_mutex对象来创建一个可超时等待的互斥锁。例如:

#include <mutex>
//这里定义了一个名为mtx的std::timed_mutex对象,用于保护某个共享资源的访问。
std::timed_mutex mtx;

std::timed_mutex的主要方法和std::mutex相同,包括lock()、unlock()和try_lock()。不同的是,std::timed_mutex提供了try_lock_for()和try_lock_until()这两个方法,用于在指定的时间范围内尝试获取互斥锁。


try_lock_for()方法允许线程尝试在指定的时间段内获取互斥锁,如果在指定时间内无法获取锁,则返回false。例如:

std::timed_mutex mtx;
std::chrono::milliseconds timeout(100);

if (mtx.try_lock_for(timeout)) 
{
    // 成功获取锁
    // ...
    mtx.unlock(); // 释放锁
} 
else 
{
    // 超时等待,未能获取锁
    // ...
}

try_lock_until()方法允许线程尝试在指定的时间点之前获取互斥锁,如果在指定时间点之前无法获取锁,则返回false。例如:

std::timed_mutex mtx;
std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::milliseconds(100);

if (mtx.try_lock_until(deadline)) 
{
    // 成功获取锁
    // ...
    mtx.unlock(); // 释放锁
} 
else 
{
    // 超时等待,未能获取锁
    // ...
}

4. std::recursive_timed_mutex (限时等待递归互斥锁)

std::recursive_timed_mutex是C++11标准库中提供的一个可递归、可超时等待的互斥锁类型,它是std::timed_mutex的另一个版本。与std::timed_mutex一样,std::recursive_timed_mutex用于实现线程间的互斥访问,但它允许同一线程多次获取锁,从而避免死锁等问题。std::recursive_timed_mutex定义在<mutex>头文件中。和std::timed_mutex类似,可以通过定义std::recursive_timed_mutex对象来创建一个可递归、可超时等待的互斥锁。例如:

#include <mutex>
//这里定义了一个名为mtx的std::recursive_timed_mutex对象,用于保护某个共享资源的访问。
std::recursive_timed_mutex mtx;

std::recursive_timed_mutex的主要方法和std::timed_mutex相同,包括lock()unlock()try_lock()。不同的是,std::recursive_timed_mutex允许同一线程多次获取锁,从而避免死锁等问题。例如:

void foo()
{
    std::unique_lock<std::recursive_timed_mutex> lock(mtx);
    // ...
    bar(); // 调用另一个函数
    // ...
}

void bar()
{
    std::unique_lock<std::recursive_timed_mutex> lock(mtx); // 可以再次获取锁
    // ...
}

在上面的例子中,foo()函数和bar()函数都使用了std::unique_lock对象来获取mtx互斥锁。由于std::recursive_timed_mutex允许同一线程多次获取锁,因此bar()函数可以再次获取锁,而不会导致死锁等问题。


与std::timed_mutex类似,std::recursive_timed_mutex也提供了try_lock_for()和try_lock_until()方法,用于在指定的时间范围内尝试获取锁。例如:

std::recursive_timed_mutex mtx;
std::chrono::milliseconds timeout(100);

if (mtx.try_lock_for(timeout)) 
{
    // 成功获取锁
    // ...
    mtx.unlock(); // 释放锁
} 
else 
{
    // 超时等待,未能获取锁
    // ...
}

🚨注意由于std::recursive_timed_mutex允许同一线程多次获取锁,因此在释放锁之前,必须将锁计数器减少到零。否则,其他线程将无法获取到锁,从而导致死锁等问题

三、总结

在使用互斥锁时,需要注意正确地加锁和解锁,以避免资源竞争和死锁等问题的发生。在大多数情况下,应优先选择最简单的互斥锁类型std::mutex,只有在需要递归、限时等待功能时才考虑其他类型。选择互斥锁类型应根据具体需求和场景来进行。

通过合理使用互斥锁,可以保证多线程程序的正确性和稳定性,提高多线程程序的性能和并发能力

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

目录
相关文章
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
185 0
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
555 12
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
192 0
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
264 0
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)