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 ...
随机推荐
- 20155235 《网络攻防》 实验九 Web安全基础
20155235 <网络攻防> 实验九 Web安全基础 实验内容 SQL注入攻击 XSS攻击 CSRF攻击 WebGoat WebGoat是OWASP组织研制出的用于进行web漏洞实验的应 ...
- FAT32文件系统学习(1) —— BPB的理解
FAT 32 文件系统学习 1.本文的目标 本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式.虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT3 ...
- CF 55 D. Beautiful numbers
D. Beautiful numbers 链接 题意: 求[L,R]中多少个数字可以整除它们的每一位上的数字. 分析: 要求模一些数字等于0等价于模它们的lcm等于0,所以可以记录当前出现的数字的lc ...
- manjaro安装软件
fcitx 安装以下包 fcitx-googlepinyin kcm-fcitx 安装了输入法之后,还要在/etc/profile或~/.xprofile里添加如下内容: export GTK_IM_ ...
- web前端开发分享-css,js入门篇
学习没有捷径,但学习是有技巧与方法. 一,css入门篇: 推荐书籍:css哪些事儿,精通css. 理由:css那些事儿,他是一本介绍css基础类的书,是入门的经典读物. 系统的介绍了css的选 ...
- B1029 旧键盘 (20 分)
20/20,第一次没调试就过了. #include<bits/stdc++.h> using namespace std; /* 1.standardize 2.put to the se ...
- 初始化Weex项目遇到的问题记录
Weex 提供了一个命令行工具 weex-toolkit 来帮助开发者使用 Weex.它可以用来快速创建一个空项目.初始化 iOS 和 Android 开发环境.调试.安装插件等操作. 目前 weex ...
- 1080. Graduate Admission (30)-排序
先对学生们进行排序,并且求出对应排名. 对于每一个学生,按照志愿的顺序: 1.如果学校名额没满,那么便被该学校录取,并且另vis[s][app[i].ranks]=1,表示学校s录取了该排名位置的学生 ...
- asp.net 网页拉伸 到300%不变形方法一
网页拉伸到300%控件和表格不会出现太大变形 方法: 1.对主页面采用百分比宽度(Width="100%") 2.对于表格使用百分比宽度,包括表格宽度和表格中顶端td宽度 3.对t ...
- JDBC的编码步骤
0.前提:拷贝数据库的驱动到构建路径中(classpath) 1.注册驱动 2.获取与数据库的链接 3.创建代表SQL语句的对象 4.执行SQL语句 5.如果是查询语句,需要遍历结果集 6.释放占用的 ...