首页 > 编程语言 >C++ folly库解读(三)Synchronized —— 比std::lock_guard/std::unique_lock更易用、功能更强大的同步机制

C++ folly库解读(三)Synchronized —— 比std::lock_guard/std::unique_lock更易用、功能更强大的同步机制

时间:2022-11-16 13:57:19浏览次数:36  
标签:std Synchronized lock folly wlock auto

目录

  • 传统同步方案的缺点
  • folly/Synchronized.h 简单使用
  • Synchronized的模板参数
  • withLock()/withRLock()/withWLock() —— 更易用的加锁方式
  • 升级锁
  • ulock()和 withULockPtr()
  • Timed Locking
  • Synchronized 与 std::condition_variable
  • acquireLocked() —— 同时锁多个数据
  • 使用一把锁,锁多个数据
  • struct
  • std::tuple
  • Benchmark

folly/Synchronized.h 提供了一种更简单、更不容易出错的同步机制,可以用来替代传统 C++标准库中使用较复杂、较容易出错的同步机制。

传统同步方案的缺点

一般是将需要同步的数据和锁一一配对,即 —— associate mutexes with data, not code :

class RequestHandler {
  ...
  std::mutex requestMutex_;
  RequestQueue requestQueue_;

  processRequest(const Request& request);
};

void RequestHandler::processRequest(const Request& request) {
  std::lock_guard<std::mutex> lg(requestMutex_);
  requestQueue_.push_back(request);
}

然而,操作这些数据成员,开发人员必须注意,正确的获取锁、获取正确的锁。

一些常见的错误包括:

  • 操作数据之前没有获取锁。
  • 获取了不配对的锁,这个锁不是用来锁这个数据的。
  • 获取了读锁,但是试图去修改数据。
  • 获取了写锁,但是对数据只有 const access.

一般在使用时,需要提醒开发人员:“别忘了 xxxx”,那一般都会出错,比如 new 的对象别忘了 delete : )

folly/Synchronized.h 简单使用

上面的代码可以用 folly/Synchronized.h 重写为:

class RequestHandler {
  folly::Synchronized<RequestQueue> requestQueue_;

  processRequest(const Request& request);
};

void RequestHandler::processRequest(const Request& request) {
  requestQueue_.wlock()->push_back(request);
}

为什么 folly/Synchronized.h 更加有效呢?

  • 与传统使用方式不同,这里锁和数据是结合成了一个对象 —— requestQueue_。传统方案中,需要寻找锁和数据的配对关系。
  • 几乎不可能在不获取锁的情况下,去操作数据,还是因为它们被封装成了一个对象。传统方案加不加锁全靠自觉。
  • 在 push_back 后,锁立即被释放。

如果在临界区有多个操作,那么可以使用如下方法:

{
      auto lockedQueue = requestQueue_.wlock();
      lockedQueue->push_back(request1);
      lockedQueue->push_back(request2);
}

wlock 返回一个 LockedPtr 对象,这个对象可以被理解为指向数据成员的指针。只有这个对象存在,那么锁就会被锁住,所以最好为这个对象显示定义一个 scope.

更好的方式,是使用 lambdas :

void RequestHandler::processRequest(const Request& request) {
      requestQueue_.withWLock([&](auto& queue "&") {
        // withWLock() automatically holds the lock for the
        // duration of this lambda function
        queue.push_back(request);
      });
}

使用 withWLock 配合 lambdas 强制定义了一个 scope,更清晰。

Synchronized的模板参数

Synchronized 有两个模板参数,数据类型和锁类型:

template <class T, class Mutex = SharedMutex>

如果不指定第二个模板参数,默认是 folly::SharedMutex。只要被 folly::LockTraits 支持的都可以使用,比如 std::mutex、std::recursive_mutex、std::timed_mutex,。std::recursive_timed_mutex、folly::SharedMutex、folly::RWSpinLock、folly::SpinLock.

根据锁类型的不同,Synchronized 会提供不同的 API:

  • 共享锁和升级锁:如果存在 lock_shared()成员函数,Synchronized 会提供 wlock(),rlock(),ulock()三个方法来获取不同的锁类型。其中,rlock()只提供对数据成员 const access.
  • 排他锁:lock()

withLock()/withRLock()/withWLock() —— 更易用的加锁方式

withLock()在上面提到过了,可以用来替代 lock()。在持有锁的期间,执行一个 lambda 或者 function. withRLock()/withWLock()同理可以替代 rlock()/wlock().

我们再详细说一下这种方式的好处。下面的函数将 vector 里的所有元素都 double:

auto locked = vec.lock();
for (int& n : *locked) {
    n *= 2;
}

使用 lock()/wlock()/rlock()的一个重要注意事项:一个指向数据的指针或者引用,它的生命周期一定不要比 LockedPtr 对象长(lock()/wlock()/rlock()的返回值类型)。 如果我们将上面的例子这样写就会出问题:

// No. NO. NO!
for (int& n : *vec.wlock()) {
      n *= 2;
}

vec.wlock()返回的 LockPtr 对象在 range iterators 建立后就销毁了(详细解释见 Range-based for loop Temporary range expression 小节),range iterators 指向了 vector data,但此时锁已经被释放。想想如果要 debug 这种问题,会用多少时间

标签:std,Synchronized,lock,folly,wlock,auto
From: https://www.cnblogs.com/arthurzyc/p/16895623.html

相关文章

  • C++ Folly库解读(零) Fbstring—— 一个完美替代std::string的库
     在引入fbstring之前,我们首先再回顾一下string常见的三种实现方式。string常见的三种实现方式string中比较重要的3个字段:char*data.指向存放字符串的首地址(......
  • 第2-3-3章 文件处理策略-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss
    目录5.2文件处理策略5.2.1FileStrategy5.2.2AbstractFileStrategy5.2.3LocalServiceImpl5.2.4FastDfsServiceImpl5.2.5AliServiceImpl5.2.6MinioServiceImpl5.2文......
  • 020_Lock和Condition
    目录传统synchronized未用同步方法时使用同步方法时Lock接口应用测试Synchronized和Lock的区别Synchronized版-生产者和消费者问题Lock版-生产者和消费者问题Condition精......
  • 080_阻塞队列 BlockingQueue
    目录简介演示代码抛出异常add()添加元素队列已满时抛出异常remove()移除元素为空时抛出异常有返回值,不抛出异常offer()添加元素队列已满时返回false不抛异常poll()移除......
  • 070_读写锁 ReadWriteLock
    目录简介演示代码不加锁演示读写锁演示简介:::info读可以被多线程同时读,写的时候只能有一个线程去写读-读,可以共存读-写,不能共存写-写,不能共存读锁是共享锁,多个线程可......
  • Interlocked 用法
    一.多线程的线程安全多线程安全问题原因是在cpu执行多线程时,在执行的过程中可能随时切换到其他的线程上执行,为多个线程同时操作同一个变量使用二.Interlocked的特点Interl......
  • SAP ABAP FICO FAGLL03H CODING BLOCK新增自定义字段
    1、SGLPOS_N_GL_CT、SGLPOS_N_CT两个结构新增自定义字段  2、执行t-code:HDBVIEWS  3、实施增强 FAGL_LIB  4、使用selectdata方法 5、代码示例:......
  • Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
     1、出现问题的原因:我前面没在VisualStudio建立C++工程时,直接用命名行编译C运行时是不会有这个问题,后面我就直接用C++建立工程时运行就报这个错误了。2、原因分析:该错......
  • Java 同步锁ReentrantLock与抽象同步队列AQS
    AbstractQueuedSynchronizer抽象同步队列,它是个模板类提供了许多以锁相关的操作,常说的AQS指的就是它。AQS继承了AbstractOwnableSynchronizer类,AOS用于保存线程对象,保存什......
  • 第2-3-2章 环境搭建-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss
    目录5.文件服务开发5.1环境搭建5.1.1数据库环境搭建5.1.2Nacos环境搭建5.1.3Nginx环境搭建5.1.4maven工程环境搭建5.文件服务开发全套代码及资料全部完整提供,点此......