C++并发编程 互斥和同步
C++并发编程 异步任务(async)
线程基本的互斥和同步工具类, 主要包括:
std::mutex 类
std::recursive_mutex 类
std::timed_mutex 类
std::recursive_timed_mutex 类
std::lock_guard 类型模板
std::unique_lock 类型模板
std::lock 函数模板
std::once_flag 类
std::call_once 函数模板
std::mutex 类
std::mutex 上锁须要调用 lock() 或 try_lock(), 当有一个线程获取了锁, 其它线程想要取得此对象的锁时, 会被阻塞(lock)或失败(try_lock). 当线程完成共享数据的保护后, 需要调用 unlock 进行释放锁.
std::mutex 不支嵌套, 如果两次调用 lock, 会产生未定义行为.
std::recursive_mutex 类
使用方法同 std::mutex, 但 std::recursive_mutex 支持一个线程获取同一个互斥量多次,而没有对其进行一次释放. 但是同一个线程内, lock 与 unlock 次数要相等, 否则其它线程将不能取得任何机会.
其原理是, 调用 lock 时, 当调用线程已持有锁时, 计数加1; 调用 try_lock 时, 尝试取得锁, 失败时不会阻塞, 成功时计数加1; 调用 unlock 时, 计数减1, 如果是最后一个锁时, 释放锁.
需要注意的是: 调用 try_lock时, 如果当前线程未取得锁, 即使没有别的线程取得锁, 也有可能失败.
std::timed_mutex 类
std::timed_mutex 在 std::mutex 的基础上支持让锁超时. 上锁时可以调用 try_lock_for, try_lock_until 设置超时值.
try_lock_for 的参数是需要等待的时间, 当参数小于等于0时会立即返回, 效果和使用 try_lock 一样.
try_lock_until 传入的参数不能小于当前时间, 否则会立即返回, 效果和使用 try_lock 一样. 实际上 try_lock_for 内部也是调用 try_lock_until 实现的.
tm.try_lock_for(std::chrono::milliseconds(1000)) 与 tm.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1000)) 等价, 都是等待1s.
std::recursive_timed_mutex 类
std::recursive_timed_mutex 在 std::recursive_mutex 的基础上, 让锁支持超时.
用法同 std::timed_mutex, 超时原理同 std::recursive_mutex.
std::lock_guard 类型模板
std::lock_guard 类型模板为基础锁包装所有权. 指定的互斥量在构造函数中上锁, 在析构函数中解锁.
这就为互斥量锁部分代码提供了一个简单的方式: 当程序运行完成时, 阻塞解除, 互斥量解锁(无论是执行到最后, 还是通过控制流语句break或return, 亦或是抛出异常).
std::lock_guard 不支持拷贝构造, 拷贝赋值和移动构造.
std::unique_lock 类型模板
std::unique_lock 类型模板比 std::loc_guard 提供了更通用的所有权包装器.
std::unique_lock 可以调用 unlock 释放锁, 而后当再次需要对共享数据进行访问时再调用 lock(), 但是必须注意一次 lock 对应一次 unlock, 不能连续多次调用同一个 lock 或 unlock.
std::unique_lock 不支持拷贝构造和拷贝赋值, 但是支持移动构造和移动赋值.
std::unique_lock 比 std::loc_guard 还增加了另外几种构造方式:
unique_lock(_Mutex& _Mtx, adopt_lock_t) 构建持有锁实例, 其不会调用 lock 或 try_lock, 但析构时默认会调用 unlock.
unique_lock(_Mutex& _Mtx, defer_lock_t) 构建非持有锁实例, 其不会调用 lock 或 try_lock, 如果没有使用 std::lock 等函数修改标志, 析构时也不会调用 unlock.
unique_lock(_Mutex& _Mtx, try_to_lock_t) 尝试从互斥量上获取锁, 通过调用 try_lock
unique_lock(_Mutex& _Mtx, const chrono::duration<_Rep, _Period>& _Rel_time) 在给定时间长度内尝试获取锁
unique_lock(_Mutex& _Mtx, const chrono::time_point<_Clock, _Duration>& _Abs_time) 在给定时间点内尝试获取锁
bool owns_lock() const 检查是否拥有一个互斥量上的锁
std::lock 函数模板
std::lock 函数模板提供同时锁住多个互斥量的功能, 且不会有因改变锁的一致性而导致的死锁. 其声明如下:
template<typename LockableType1,typename... LockableType2> void lock(LockableType1& m1,LockableType2& m2...);
// 使用互斥量保护代码
typedef std::lock_guard<std::mutex> MutexLockGuard;
typedef std::unique_lock<std::mutex> UniqueLockGuard; class Func
{
int i;
std::mutex& m;
public:
Func(int i_, std::mutex& m_) : i(i_), m(m_) {}
void operator() ()
{
//MutexLockGuard lk(m);
UniqueLockGuard lk(m);
for (unsigned j = ; j < ; ++j)
{
std::cout << i << " ";
}
std::cout << std::endl;
}
}; std::mutex m;
std::vector<std::thread> threads; for (int i = ; i < ; i++)
{
Func f(i, m);
threads.push_back(std::thread(f));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 对每个线程调用join()
// 同时对多个 mutex 上锁
std::mutex m1;
std::mutex m2;
//std::unique_lock<std::mutex> lock_a(m1, std::defer_lock);
//std::unique_lock<std::mutex> lock_b(m2, std::defer_lock); // std::def_lock 留下未上锁的互斥量
//std::lock(lock_a, lock_b); // 互斥量在这里上锁, 并修改对象的上锁标志
std::lock(m1, m2); // 锁住两个互斥量
std::lock_guard<std::mutex> lock_a(m1, std::adopt_lock); // std::adopt_lock 参数表示对象已经上锁,因此不会调用 lock 函数
std::lock_guard<std::mutex> lock_b(m2, std::adopt_lock);
std::call_once 函数模板
如果多个线程需要同时调用某个函数,std::call_once 可以保证多个线程对该函数只调用一次, 并且是线程安全的.
线程安全的延迟初始化
-- 使用 std::call_once 和 std::once_flag
考虑下面的代码, 每个线程必须等待互斥量,以便确定数据源已经初始化, 这导致了线程资源产生不必要的序列化问题.
std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo()
{
std::unique_lock<std::mutex> lk(resource_mutex); // 所有线程在此序列化
if (!resource_ptr)
{
resource_ptr.reset(new some_resource); // 只有初始化过程需要保护
}
lk.unlock();
resource_ptr->do_something();
}
使用双重检查锁优化上述代码, 指针第一次读取数据不需要获取锁, 并且只有在指针为NULL时才需要获取锁; 然后, 当获取锁之后, 指针会被再次检查一遍(这就是双重检查的部分), 避免另一的线程在第一次检查后再做初始化, 并且让当前线程获取锁.
这样同样存在问题, 即潜在的条件竞争, 因为外部的读取锁①时没有与内部的写入锁进行同步③, 因此就会产生条件竞争,这个条件竞争不仅覆盖指针本身, 还会影响到其指向的对象:
即使一个线程知道另一个线程完成对指针进行写入, 它可能没有看到新创建的some_resource实例, 然后调用do_something()④后, 得到不正确的结果. 这在C++标准中被指定为“未定义行为”.
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(); //
}
C++的解决方法:
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();
}
线程安全类成员的延迟初始化
class X
{
private:
connection_info connection_details;
connection_handle connection;
std::once_flag connection_init_flag;
void open_connection()
{
connection = connection_manager.open(connection_details);
}
public:
X(connection_info const& connection_details_) : connection_details(connection_details_) {}
void send_data(data_packet const& data) //
{
std::call_once(connection_init_flag, &X::open_connection, this); //
connection.send_data(data);
}
data_packet receive_data() //
{
std::call_once(connection_init_flag, &X::open_connection, this); //
return connection.receive_data();
}
};
boost::shared_lock
读者-写者锁 boost::shared_lock, 允许两中不同的使用方式:一个“作者”线程独占访问和共享访问, 让多个“读者”线程并发访问. (C++11标准不支持)
其性能依赖与参与其中的处理器数量, 也与读者和写者线程的负载有关. 一种典型的应用:
#include <map>
#include <string>
#include <mutex>
#include <boost/thread/shared_mutex.hpp>
class dns_entry;
class dns_cache
{
std::map<std::string, dns_entry> entries;
mutable boost::shared_mutex entry_mutex;
public:
dns_entry find_entry(std::string const& domain) const
{
boost::shared_lock<boost::shared_mutex> lk(entry_mutex); //
std::map<std::string, dns_entry>::const_iterator const it =
entries.find(domain);
return (it == entries.end()) ? dns_entry() : it->second;
}
void update_or_add_entry(std::string const& domain,
dns_entry const& dns_details)
{
std::lock_guard<boost::shared_mutex> lk(entry_mutex); //
entries[domain] = dns_details;
}
};
C++并发编程 互斥和同步的更多相关文章
- <<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步(1)
<<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步 并发问题是所有问题的基础,也是操作系统设计的基础.并发包括很多设计问题,其中有进程间通信,资源共享与竞争,多个 ...
- 【Java并发编程二】同步容器和并发容器
一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...
- 网络编程基础----并发编程 ---守护进程----同步锁 lock-----IPC机制----生产者消费者模型
1 守护进程: 主进程 创建 守护进程 辅助主进程的运行 设置进程的 daemon属性 p1.daemon=True 1 守护进程会在主进程代码执行结束后就终止: 2 守护进程内无法再开启子进程 ...
- Python并发编程-进程 线程 同步锁 线程死锁和递归锁
进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...
- 并发编程(ReentrantLock&&同步模式之顺序控制)
4.13 ReentrantLock 相对于 synchronized 它具备如下特点 可中断 可以设置超时时间 可以设置为公平锁 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待 与 ...
- 并发编程---互斥锁---互斥锁与join的区别
互斥锁 互斥锁:就是把多个进程并发,修改成一块共享数据的操作变成串行,保证是一个一个来修改的. 缺点:效率低,加锁过程复杂 优点:增加了安全性 from multiprocessing import ...
- java并发编程:线程同步和锁
一.锁的原理 java中每个对象都有一个内置锁.当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this)有关的锁.获得一个对象的锁也称为获取锁,当程序运 ...
- Java并发编程3-抽象同步队列AQS详解
AQS是AtractQueuedSynchronizer(队列同步器)的简写,是用来构建锁或其他同步组件的基础框架.主要通过一个int类型的state来表示同步状态,内部有一个FIFO的同步队列来实现 ...
- java并发编程基础——线程同步
线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...
随机推荐
- day05 字典 dict
今日内容: 字典 成对的保存数据. 以key:value的形式保存 用{}表示,每一项内容都是key:value, 每项数据之间用逗号隔开 字典中的key是不能重复的. 存储是依靠着key来计算的. ...
- 【Alpha】阶段第五次Scrum Meeting
[Alpha]阶段第五次Scrum Meeting 工作情况 团队成员 今日已完成任务 明日待完成任务 刘峻辰 增加课程接口 增加教师接口 赵智源 整合前端进行部署 构建后端测试点测试框架 肖萌威 编 ...
- iOS开发 常见错误
一.NSAppTransportSecurity 错误提示:NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL ...
- 浅谈Java变量的初始化顺序详解
规则1(无继承情况下):对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是(静态变量.静态初始化块)>(变量.初始化块)>构造器证明代码: 复制代码 代码如下: p ...
- 网页调用vlc并播放网络视频
环境:windows/android/ios windows端保存以下内容为reg文件并运行 Windows Registry Editor Version 5.00 [HKEY_CLASSES_RO ...
- javascript方法扩展
String.prototype.startWith = function(str){ return str.indexOf(str) == 0; }; var str = "abc&quo ...
- 未能加载文件或程序集“log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821”或它的某一个依赖项。系统找不到指定的文件。
在网上找了很久,很多个地方让修改配置文件,也有重装log4net的. 如文章:使用Common.Logging与log4net的组件版本兼容问题 我检查下发现项目中的package包中的Log4net ...
- C语言文法阅读与理解
<翻译单元>--><外部声明>--><函数定义>|<申报> <函数定义>--><声明说明符>-->< ...
- CodeForces Round #527 (Div3) D1. Great Vova Wall (Version 1)
http://codeforces.com/contest/1092/problem/D1 Vova's family is building the Great Vova Wall (named b ...
- windows查看端口占用指令
1.Windows平台 在windows命令行窗口下执行: 1.查看所有的端口占用情况 C:\>netstat -ano 协议 本地地址 外部地址 ...