c++并发编程之互斥锁(mutex)的使用方法
1. 多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁)。
引用 cppreference 的介绍:
The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.
方法1:直接操作 mutex,即直接调用 mutex 的 lock / unlock 函数
此例顺带使用了 boost::thread_group 来创建一组线程。
#include <iostream>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp> boost::mutex mutex;
int count = ; void Counter() {
mutex.lock(); int i = ++count;
std::cout << "count == " << i << std::endl; // 前面代码如有异常,unlock 就调不到了。
mutex.unlock();
} int main() {
// 创建一组线程。
boost::thread_group threads;
for (int i = ; i < ; ++i) {
threads.create_thread(&Counter);
} // 等待所有线程结束。
threads.join_all();
return ;
}
方法2:使用 lock_guard 自动加锁、解锁。原理是 RAII,和智能指针类似
#include <iostream>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp> boost::mutex mutex;
int count = ; void Counter() {
// lock_guard 在构造函数里加锁,在析构函数里解锁。
boost::lock_guard<boost::mutex> lock(mutex); int i = ++count;
std::cout << "count == " << i << std::endl;
} int main() {
boost::thread_group threads;
for (int i = ; i < ; ++i) {
threads.create_thread(&Counter);
} threads.join_all();
return ;
}
方法3:使用 unique_lock 自动加锁、解锁unique_lock 与 lock_guard 原理相同,但是提供了更多功能(比如可以结合条件变量使用)。
注意:mutex::scoped_lock 其实就是 unique_lock<mutex> 的 typedef。
#include <iostream>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp> boost::mutex mutex;
int count = ; void Counter() {
boost::unique_lock<boost::mutex> lock(mutex); int i = ++count;
std::cout << "count == " << i << std::endl;
} int main() {
boost::thread_group threads;
for (int i = ; i < ; ++i) {
threads.create_thread(&Counter);
} threads.join_all();
return ;
}
方法4:为输出流使用单独的 mutex
这么做是因为 IO 流并不是线程安全的!
如果不对 IO 进行同步,此例的输出很可能变成:
count == count == 2count ==
count ==
因为在下面这条输出语句中:
std::cout << "count == " << i << std::endl;
输出 "count == " 和 i 这两个动作不是原子性的(atomic),可能被其他线程打断。
#include <iostream>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/lock_guard.hpp> boost::mutex mutex;
boost::mutex io_mutex;
int count = ; void Counter() {
int i;
{
boost::unique_lock<boost::mutex> lock(mutex);
i = ++count;
} {
boost::unique_lock<boost::mutex> lock(io_mutex);
std::cout << "count == " << i << std::endl;
}
} int main() {
boost::thread_group threads;
for (int i = ; i < ; ++i) {
threads.create_thread(&Counter);
} threads.join_all();
return ;
}
2. 保护共享数据的替代设施
2.1 保护共享数据的初始化过程
丑陋的代码:
void undefined_behaviour_with_double_checked_locking()
{
if(!resource_ptr) //
{
std::lock_guard<std::mutex> lk(resource_mutex);
if(!resource_ptr) //
{
resource_ptr.reset(new some_resource); //
}
}
resource_ptr->do_something(); //
}
这个模式为什么声名狼藉呢?因为这里有潜在的条件竞争,因为外部的读取锁①没有与内部的
写入锁进行同步③。因此就会产生条件竞争,这个条件竞争不仅覆盖指针本身,还会影响到其
指向的对象;即使一个线程知道另一个线程完成对指针进行写入,它可能没有看到新创建的
some_resource实例,然后调用do_something()④后,得到不正确的结果。
C++标准库提供了 std::once_flag 和 std::call_once 来处理这种情况。比起锁住互斥量,并显式的检查指
针,每个线程只需要使用 std::call_once ,在 std::call_once 的结束时,就能安全的知道指
针已经被其他的线程初始化了。使用 std::call_once 比显式使用互斥量消耗的资源更少,特
别是当初始化完成后。
std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag; //
void init_resource()
{
resource_ptr.reset(new some_resource);
}
void foo()
{
std::call_once(resource_flag,init_resource); // 可以完整的进行一次初始化
resource_ptr->do_something();
}
2.2 保护很少更新的数据结构
虽然更新频度很低,但更新也是有可能发生的,并且当这个可缓存被多个线程访问,这个缓
存就需要适当的保护措施,来对其处于更新状态时进行保护,也为了确保线程读到缓存中的
有效数据。
使用一
个 std::mutex 来保护数据结构,这的确有些反应过度,因为在没有发生修改时,它将削减并
发读取数据的可能性;这里需要另一种不同的互斥量。这种新的互斥量常被称为“读者-写者
锁”(reader-writer mutex),因为其允许两中不同的使用方式:一个“作者”线程独占访问和共
享访问,让多个“读者”线程并发访问。
新的C++标准库应该不提供这样的互斥量,Boost库提供了boost::shared_mutex。
3.3 嵌套锁
C++标准库提供了 std::recursive_mutex 类。其功能与 std::mutex 类似,除了你可以从
同一线程的单个实例上获取多个锁。在互斥量锁住其他线程前,你必须释放你拥有的所有
锁,所以当你调用lock()三次时,你也必须调用unlock()三次。正确使
用 std::lock_guard<std::recursive_mutex> 和 std::unique_lock<std::recursice_mutex> 可以帮
你处理这些问题。
c++并发编程之互斥锁(mutex)的使用方法的更多相关文章
- python 并发编程 多进程 互斥锁 目录
python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别
- python 并发编程 多进程 互斥锁
运行多进程 每个子进程的内存空间是互相隔离的 进程之间数据不能共享的 一 互斥锁 但是进程之间都是运行在一个操作系统上,进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终 ...
- C++ 并发编程之互斥锁和条件变量的性能比较
介绍 本文以最简单生产者消费者模型,通过运行程序,观察该进程的cpu使用率,来对比使用互斥锁 和 互斥锁+条件变量的性能比较. 本例子的生产者消费者模型,1个生产者,5个消费者. 生产者线程往队列里放 ...
- python 并发编程 多线程 互斥锁
互斥锁 并行变成串行,牺牲效率 保证数据安全,实现局部串行 保护不同的数据,应该加不同的锁 现在一个进程 可以有多个线程 所有线程都共享进程的地址空间 实现数据共享 共享带来问题就会出现竞争 竞争就会 ...
- python 并发编程 多进程 互斥锁与join区别
互斥锁与join 互斥锁和join都可以把并发变成串行 以下代码是用join实现串行 from multiprocessing import Process import time import js ...
- 并发编程 Process 互斥锁
进程理论 程序与进程的区别 ''' 程序不是存在硬盘上的代码,相对来说是静态的 进程表示程序在执行的过程,是动态的 ''' 进程的调度 先来先服务调度算法 '''对长作业有利,对短作业无益''' 短作 ...
- 互斥锁Mutex与信号量Semaphore的区别
转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...
- Golang之并发资源竞争(互斥锁)
并发本身并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙的问题. package main import ( "fmt" &q ...
- 线程锁(互斥锁Mutex)
线程锁(互斥锁Mutex) 一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况? # -*- cod ...
随机推荐
- 20155305乔磊《网络对抗》逆向及Bof基础
20155305乔磊<网络对抗>逆向及Bof基础 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何 ...
- windows系统中Dotnet core runtime 安装后,无法启动次程序,因为计算机中丢失api-ms-win-crt-runtime-l1-1-0.dll的解决方法
因为dotnet core runtime依赖vc++2015,如果系统未安装vc++2015则会报上面的错误 解决方案:先下载安装vc++2015再安装dotnet core runtime, vc ...
- CS229笔记:分类与逻辑回归
逻辑回归 对于一个二分类(binary classification)问题,\(y \in \left\{0, 1\right\}\),如果直接用线性回归去预测,结果显然是非常不准确的,所以我们采用一 ...
- TLV5620参考电压的问题
1. TLV5620参考电压的,上面红线的VID的意思应该是引脚(REFA-REFD)输入的电压值(3.3V),下面的应该是实际参考值,根据实际测试VID=3.3V的时候,Vref=2.2V,至于为什 ...
- Android Studio Xposed模块编写(一)
1.环境说明 本文主要参考https://my.oschina.net/wisedream/blog/471292?fromerr=rNPFQidG的内容,自己实现了一遍,侵权请告知 已经安装xpos ...
- laraver框架学习
最近开始学习laravel框架,这个框架在国外很流行,近些年开始在国内流行.自己而是刚开始学习这个框架. 使用composer 更新系统内的依赖包 在终端输入:composer update Entr ...
- 深入浅出etcd系列 – 心跳和选举
作者:宝爷 校对:DJ 1.绪论 etcd作为华为云PaaS的核心部件,实现了PaaS大多数组件的数据持久化.集群选举.状态同步等功能.如此重要的一个部件,我们只有深入地理解其架构设计和内部工作机制, ...
- [转]申瓯 JSY2000-06 程控电话交换机呼叫转移设置
说明:若申瓯程控电话交换机分机有事不在位置上或遇忙分机正忙时为使某些重要来话不丢失,可设置将呼入本机的电话转移至其他分机及公网固定电话或手机.电话交换机使用了本功能不管分机用户在什么地方都能接听到办公 ...
- Unity特殊路径
Resources: Resources文件可以在根目录下,也可以在子目录下,只要叫Resources就好.Resources目录下所有资源将被打包进游戏存放资源的archive中,Resources ...
- 深入理解docker信号机制以及dumb-init的使用
一.前言 ● 容器中部署的时候往往都是直接运行二进制文件或命令,这样对于容器的作用更加直观,但是也会出现新的问题,比如子进程的资源回收.释放.托管等,处理不好,便会成为可怕的僵尸进程 ● 本文主要讨论 ...