【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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

目录
相关文章
|
4天前
|
C++ 存储 编译器
|
3天前
|
编译器 C++
C++入门(命名空间)
C++入门(命名空间)
|
4天前
|
C++ 编译器 程序员
C++ 从零基础到入门(3)—— 函数基础知识
C++ 从零基础到入门(3)—— 函数基础知识
|
4天前
|
C++ 存储
C++从零基础到入门(2)—— (if、switch、for、while语句)
C++从零基础到入门(2)—— (if、switch、for、while语句)
C++从零基础到入门(2)—— (if、switch、for、while语句)
|
4天前
|
编译器 C语言 C++
C++入门基础-2
C++入门基础
12 3
|
4天前
|
C语言 C++
C++入门基础-1
C++入门基础
18 1
|
4天前
|
自然语言处理 编译器 C语言
【C++】C++ 入门 — 命名空间,输入输出,函数新特性
本文章是我对C++学习的开始,很荣幸与大家一同进步。 首先我先介绍一下C++,C++是上个世纪为了解决软件危机所创立 的一项面向对象的编程语言(OOP思想)。
36 1
【C++】C++ 入门 — 命名空间,输入输出,函数新特性
|
4天前
|
存储 安全 编译器
【C++从练气到飞升】03---C++入门(三)
【C++从练气到飞升】03---C++入门(三)
|
4天前
|
存储 自然语言处理 编译器
【C++从练气到飞升】02---C++入门(二)
【C++从练气到飞升】02---C++入门(二)