【C++并发编程】(三)互斥锁:std::mutex

2024-05-05 12:44
文章标签 c++ 互斥 并发 编程 std mutex

本文主要是介绍【C++并发编程】(三)互斥锁:std::mutex,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 互斥锁
    • 数据竞争
    • C++互斥锁
      • lock() 和 unlock()
      • std::lock_guard

互斥锁

数据竞争

在并发编程中,数据竞争是指多个线程同时对共享数据进行读写操作,并且至少有一个线程进行写操作,从而导致未定义的行为或结果。
下面给出一个例子:

#include <iostream>  
#include <thread>  int counter = 0; // 共享数据void increment() {  for (int i = 0; i < 100000; ++i) {  ++counter; // 修改被共享的数据}  
}  int main() {  std::thread th1(increment);  std::thread th2(increment);  th1.join();  th2.join();  // 预期输出是200000,但由于数据竞争,实际输出可能小于这个值  std::cout << "Final counter value: " << counter << std::endl;  // Final counter value: 107194return 0;  
}

两个线程t1t2可能同时对counter进行自增操作,导致counter的值增加的数量少于预期(即少于200000)。这是因为:

自增操作包含三个步骤:

  • 从内存中读取counter的当前值0。
  • 将这个值加1。
  • 将结果1写回内存中的counter。

如果两个线程同时执行这些步骤,可能会发生以下情况:

  • 线程t1读取counter的值为0。
  • 线程t2也读取counter的值为0(因为线程t1还没有将新的值写回内存)。
  • 线程t1将1写回counter。
  • 线程t2也将1写回counter(因为它之前读取的值是0)。

C++互斥锁

互斥锁(互斥量)能保护多个线程的共享资源不被同时访问。互斥锁的状态只有两种:开锁(unlocked)和闭锁(locked)。当一个任务(或线程)持有互斥锁,且该互斥锁处于闭锁状态,那么这个任务可以执行访问共享资源的代码,其他任务(或线程)则不被允许。开锁后,该线程将失去了对互斥锁的所有权,其他正在等待的线程现在有机会获取这个互斥锁,以访问共享资源。

在C++11中提供了std::mutex支持这一功能。std::mutex是一个简单的互斥锁类,它有两个主要操作:lock()unlock(),用于实现闭锁和开锁。

**lock():**当一个线程调用 lock() 函数时,它会尝试获取与该函数关联的互斥锁。如果互斥量当前没有被其他线程锁定(即它是可用的),则调用线程会成功获取锁,并继续执行其后续代码。
如果互斥量已经被其他线程锁定,则调用线程会被阻塞(即它会停止执行,直到锁变得可用)。
一旦线程获取了锁,它就可以安全地访问共享资源,而不必担心其他线程同时修改它。

unlock(): 当一个线程完成对共享资源的访问后,它应该调用 unlock() 函数来释放与该函数关联的互斥量(或其他锁)。释放锁允许其他线程获取该锁并访问共享资源。

在C++中,直接使用 lock()unlock() 函数来管理锁可能会导致一些问题,特别是当代码路径变得复杂时。为了简化锁的管理并减少出错的可能性,C++11引入了 std::lock_guardstd::unique_lock 等RAII(Resource Acquisition Is Initialization)风格的包装器,用于自动管理互斥锁的开锁和闭锁。其中,std::lock_guard 是最简单的锁包装器,它只提供了基本的锁定和解锁功能。 std::unique_lock 提供了比 std::lock_guard 更多的功能,更灵活,后续再单独介绍。

下面给出示例展示如何使用lock()unlock()以及锁包装器std::lock_guard 进行互斥锁的闭锁和开锁。

lock() 和 unlock()

#include <iostream>  
#include <thread>  
#include <mutex>  std::mutex mtx; // 创建一个互斥锁实例,用于保护共享数据
int counter = 0; // 共享数据 void increment() {  for (int i = 0; i < 100000; ++i) {  // 显式地调用了lock()和unlock()来分别获取和释放锁:mtx.lock(); // 获取锁  ++counter; // 递增操作  mtx.unlock(); // 释放锁  }  
}  int main() {  std::thread th1(increment);  std::thread th2(increment);  th1.join();  th2.join();  std::cout << "Final counter value: " << counter << std::endl;  // Final counter value: 200000return 0;  
}

std::lock_guard

#include <iostream>  
#include <thread>  
#include <mutex>  std::mutex mtx; // 互斥锁用于保护共享数据
int counter = 0; // 共享数据 void increment() {  std::unique_lock<std::mutex> lock(mtx); // 构造时自动获取锁  for (int i = 0; i < 100000; ++i) {  ++counter; // 递增操作,因为lock存在,所以此操作是安全的  }  // lock在离开作用域时自动释放锁  
}  int main() {  std::thread th1(increment);  std::thread th2(increment);  th1.join();  th2.join();  std::cout << "Final counter value: " << counter << std::endl;  // Final counter value: 200000return 0;  
}

这篇关于【C++并发编程】(三)互斥锁:std::mutex的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/961755

相关文章

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元

C/C++的OpenCV 进行图像梯度提取的几种实现

《C/C++的OpenCV进行图像梯度提取的几种实现》本文主要介绍了C/C++的OpenCV进行图像梯度提取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录预www.chinasem.cn备知识1. 图像加载与预处理2. Sobel 算子计算 X 和 Y