C++多线程环境下的构造函数

多线程的环境里,我们总不可避免要使用锁。于是一个常见的场景就是:

 1 class ObjectWithLock
 2 {
 3 private:
 4   std::mutex mtx_;
 5   SomeResType shared_res_;
 6
 7 public:
 8   // Constructor/Destructor
 9   …
10
11   void OpOnSharedRes()
12   {
13     std::lock_guard<std::mutex> lock(mtx_);
14
15     // read/write shared resource: shared_res_
16     …
17   }
18 };

问题

多线程环境下该如何实现拷贝构造函数和移动构造函数呢?

要实现默认构造函数,没有挑战。使用成员初始化列表就可以:

ObjectWithLock() : mtx_(), shared_res_() {} // 如果SomeResType有默认构造函数的话。

那么我们的拷贝构造怎么写呢?

方案一

非常有可能见到的一种解决方案是:

1 ObjectWithLock(ObjectWithLock const & rhs)
2 {
3   std::lock_guard<std::mutex> lock(rhs.mtx_);
4
5   shared_res_ = rhs.shared_res_;
6 }

优点

很明显,这个方案解决了我们的问题。

缺点

方案一的缺点也是显而易见的。就是shared_res_ = rhs.shared_res_。为什么这么说呢?

我们先来复习下成员变量初始化顺序。

C++成员变量的初始化顺序

成员变量的初始化顺序在C++里弄不好,就是各种坑。

首先我们比较容易知道的是,C++成员变量的初始化发生在构造函数之前。

那么成员变量之间初始化的顺序是如何的呢?

没有专门去注意过这个问题的童鞋,很可能会认为,成员变量的初始化顺序是有成员变量初始化列表的顺序决定的。而事实上成员变量的初始化顺序是有其声明顺序决定的。

方案一的问题

所以方案一的问题就是,shared_res_ = rhs.shared_res_,做的是赋值而不是初始化。如果我们要过分看待这个问题的话,那么结论就是这会带来性能损耗(看具体情况,对性能存在很大的影响也是有可能的)。

当然很多时候我们没有必要吹毛求疵。事实上方案一最大的问题是他有很多局限性。这些局限性要求SomeResType必须:

  1. 有无参默认构造
  2. 支持赋值操作
  3. 没有const成员变量,没有引用类型的成员变量等

如果SomeResType不支持这些前提条件,那么为了解决当前的问题,要么让SomeResType支持,要么重新设计ObjectWithLock。如果ObjectWithLock是一个正确的设计想法,那么就只能为难SomeResType了。蛋疼。

难道这个问题就无解了不成?当然不是这样的,不过如果按照C++98/03的标准来做,真的不容易。我们还是来看看C++11能给我们带来什么惊喜吧。

方案二

C++11引入了一个和构造函数相关的语法,delegate constructor,代理构造函数。一个在C#里早就存在的语法,终于C++里也有了。

所谓的delegate constructor,就是构造函数的初始化可以代理(delegates to)给该类另外一个构造函数去完成。一个简单的例子(来自C++11标准文档12.6.2,段落6)就是:

1 struct C
2 {
3   C(int c) {}     // #1: non-delegating constructor
4   C() : C(42) {}  // #2: delegate to #1
5 };

有了代理构造函数,我们的问题就容易解决了。我们要解决的问题的关键就是,在初始化成员变量前,先将rhs对象lock住。

那么原先的拷贝构造函数要做到和下面伪代码类似的事情:

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  : shared_res_(rhs.shared_res_), lock(rhs)
{}

借助代理构造函数,我们先实现一个自定义的构造函数,能够做拷贝,并且接受一把锁:

1 ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs,
2                                std::lock_guard<std::mutex> const &)
3   : shared_res_(rhs.shared_res_)
4 {}

然后很自然我就有了:

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  ObjctWithLock(rhs, std::lock_guard<std::mutex>(rhs.mtx_))
{}

问题完美解决了!

C++98/03的解决方案

C++98/03到底能不能解决这个问题呢?能!当然能!就是看你能不能想到了。

这里我就直接给结果,留给大家自己去想吧。

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  : shared_res_((std::lock_guard<std::mutex>(rhs.mtx_), rhs.shared_res_))
{}

结束语

这里就只罗列的拷贝构造函数,移动构造函数同理,留给大家自己想吧。完!

C++多线程环境下的构造函数

时间: 2024-10-15 02:15:47

C++多线程环境下的构造函数的相关文章

SQLite在多线程环境下的应用

这几天研究了一下SQLite这个嵌入式数据库在多线程环境下的应用,感觉里面的学问还挺多,于是就在此分享一下. AD: 2014WOT全球软件技术峰会北京站 课程视频发布 先说下初衷吧,实际上我经常看到有人抱怨SQLite不支持多线程.而在iOS开发时,为了不阻塞主线程,数据库访问必须移到子线程中.为了解决这个矛盾,很有必要对此一探究竟. 关于这个问题,最权威的解答当然是SQLite官网上的“Is SQLite threadsafe?”这个问答. 简单来说,从3.3.1版本开始,它就是线程安全的了

多线程环境下的线程不安全问题(1)

在不考虑多线程的情况下,很多类代码都是完全正确的,但是如果放在多线程环境下,这些代码就很容易出错,我们称这些类为 线程不安全类 .多线程环境下使用线程安全类 才是安全的. 下面是一个线程不安全类的例子: public class Account { private Integer balance; public Account(Integer balance) { super(); this. balance = balance; } public Integer getBalance() {

UNIX多线程环境下屏障功能(barrier)浅析

说起屏障这个东西,相信对于大多数朋友来说比较陌生,不过要是说起pthread_join这个函数,相信都比较熟悉.我们通常使用这个函数来等待其它线程结束,例如主线程创建一些线程,这些线程去完成一些工作,而主线程需要去等待这些线程结束.其实pthread_join就实现了一种屏障.我们可以对屏障这样理解,把屏障理解为为了协同线程之间的工作而使得某一具体线程进入等待状态的一种机制.下面我们来看看UNIX为我们提供的一个屏障——barrier. 注:与所有的线程函数一样,使用屏障barrier时需要包含

Java读写锁,多线程环境下提升效率

读写锁 package cn.sniper.thread.lock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Cache {

多线程环境下队列操作之锁的教训

之前一直在研究多线程环境下的编程方法,却很少实战体验,以至于我一提到多线程编程,我总是信心不足,又总是说不出到底哪里不明白.今天工程现场反馈了一个“老问题”,我一直担心的是DAServer的运行机制有什么我不明白的地方,DAS Toolkit中总有一部分是我没有仔细研究的,在我心中有阴影,所以工程出了问题我第一反应就是“会不会问题出在阴影里?”.结果,至今为止,我总结起来问题90%都是在自己编码部分. 我的DAServer中有一个需求,就是上送某个定点数据的速度不能太快,否则后台接收不过来.于是

单例模式在多线程环境下的lazy模式为什么要加两个if(instance==null)

刚才在看阿寻的博客”C#设计模式学习笔记-单例模式“时,发现了评论里有几个人在问单例模式在多线程环境下为什么lazy模式要加两个if进行判断,评论中的一个哥们剑过不留痕,给他们写了一个demo来告诉他们为什么. 我看了一下这个demo,确实说明了这个问题,但我认为不够直观,呵呵,于是我就稍微的改了一下. 这是剑过不留痕的demo using System; using System.Threading; namespace SingletonPattern { class Program { s

多线程环境下慎用静态变量

最近在修复一个旧的互联网应用bug,问题是程序有时候会自动拼接参数,比如正确的参数应该是f(\\d)-f(\\d)-f(\\d),而实际情况可能会出现f(\\d)-f(\\d)-f(\\d)-f(\\d)-f(\\d).查找bug的时候,从页面入手,然后研究逻辑处理,url生成规则......不断地调试,实验,应用在线下一切正常,但到了线上就会出现问题.线上页有时候刷新一下则又恢复正常.仔细研究了一下程序的逻辑,发现应该是没问题的.最后在某个地方发现程序调用了静态变量.我们都知道,程序一开始会为

设计模式——单例模式(Java)——考虑多线程环境下的线程安全问题

设计模式--单例模式(Java)--考虑多线程环境下的线程安全问题 一:单例模式概念 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例 二:单例模式的实现方式 特别注意,在多线程环境下,需要对获取对象实例的方法加对象锁(synchronized) 方式一:(懒汉式)程序执行过程中需要这个类的对象,再实例化这个类的对象 步骤: 1.定义静态私有对象 2.构造方法私有化保证在类的外部无法实例化该类的对象 3.定义对外开放的静

HttpClient在多线程环境下踩坑总结

问题现场 在多线程环境下使用HttpClient组件对某个HTTP服务发起请求,运行一段时间之后发现客户端主机CPU利用率呈现出下降趋势,而不是一个稳定的状态. 而且,从程序日志中判断有线程处于夯住的状态,应该是被阻塞了. 问题排查 一开始找不到原因,怀疑是多线程并发导致的死锁问题,但是通过代码审查并未定位到任何可能的多线程并发问题. 甚至开始怀疑是否是因为内存资源不够引起JVM频繁GC到导致业务线程被暂停,但是从GC的日志输出结果看,GC是正常的. 于是,进入一种丈二和尚摸不着头脑头脑的状态,