EC笔记:第三部分:14、在资源管理类中小心Copying行为
场景
上一节实现了智能指针,其中的拷贝构造函数和赋值运算符是通过增加/减少指针的引用计数来操作的。但是如果是管理一个独占资源呢?我们希望在一个资源使用时被锁定,在使用完毕后被释放。
#include <mutex>
#include <thread>
#include <iostream>
using
namespace std;
mutex mu;
int rc=5;
void
thread1(){
//mu.lock();
rc+=5;
cout<<"thread1:"<<rc<<endl;
//mu.unlock();
}
void
thread2(){
//mu.lock();
rc-=5;
cout<<"thread2:"<<rc<<endl;
//mu.unlock();
}
int
main(){
thread th1(thread1);
thread th2(thread2);
th1.join();
th2.join();
}
在这里,我先把互斥代码去掉,编译运行后的结果是:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
thread1:thread2:510
C:\Users\SkyFire\Desktop>a
thread1:thread2:105
C:\Users\SkyFire\Desktop>a
thread1:thread2:105
C:\Users\SkyFire\Desktop>a
thread1:thread2:510
每次的结果都不确定,因为没加互斥。
那么,把互斥加上:
#include <mutex>
#include <thread>
#include <iostream>
using
namespace std;
mutex mu;
int rc=5;
void
thread1(){
mu.lock();
rc+=5;
cout<<"thread1:"<<rc<<endl;
mu.unlock();
}
void
thread2(){
mu.lock();
rc-=5;
cout<<"thread2:"<<rc<<endl;
mu.unlock();
}
int
main(){
thread th1(thread1);
thread th2(thread2);
th1.join();
th2.join();
}
编译运行的结果是:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
thread1:10
thread2:5
C:\Users\SkyFire\Desktop>a
thread1:10
thread2:5
C:\Users\SkyFire\Desktop>a
thread1:10
thread2:5
但是某些时候,我们可能会将unlock的动作漏写(百密一疏),如下面这种:
#include <mutex>
#include <thread>
#include <iostream>
using
namespace std;
mutex mu;
int rc=5;
void
thread1(){
mu.lock();
rc+=5;
cout<<"thread1:"<<rc<<endl;
//mu.unlock();
}
void
thread2(){
mu.lock();
rc-=5;
cout<<"thread2:"<<rc<<endl;
mu.unlock();
}
int
main(){
thread th1(thread1);
thread th2(thread2);
th1.join();
th2.join();
}
这样的结果就是thread2里面的语句一直得不到执行,程序死锁。
编译运行:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
thread1:10
^C
C:\Users\SkyFire\Desktop>
可以看到,thread2一直没有执行,后面的^C是我使用Ctrl+C中断的结果。
为了避免这种情况,我们使用资源管理类。
简单的实现
一个简单的实现:
class AutoMutex{
private:
mutex μ
public:
AutoMutex(mutex &t):mu(t){
mu.lock();
}
~AutoMutex(){
mu.unlock();
}
};
这个类在构造的时候会将一个互斥量锁定,而在析构时会释放掉这个互斥量。乍一看好像没什么问题。事实上,在"正常的"情况下,这段代码可以工作的很好。
mutex mu;
void
mythread(){
AutoMutex t(mu);
cout<<"hello world"<<endl;
}
int
main(){
for(int i=0;i<10;++i)
thread(mythread).detach();
system("pause");
}
输出:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
请按任意键继续. . .
问题
但是,如果出现一些比较调皮的程序员(暂定为小明吧)。
调皮的小明写出了如下的代码:
mutex mu;
mutex mu2;
void
mythread(){
AutoMutex t(mu);
AutoMutex t2(mu2);
t2=t;
cout<<"hello world"<<endl;
}
int
main(){
for(int i=0;i<10;++i)
thread(mythread).detach();
system("pause");
}
这TM就尴尬了……小明将管理了两个不同的mutex的对象相互赋值了。不过还好,这段代码是编译不通过的(小明的奸计未能得逞)。因为mutex类是不允许复制的,他的赋值运算符是删除的。(假设mutex可以复制,会产生什么?)
而且,管理两个mutex的对象的赋值没有任何意义,这个对象就是创建与销毁,并没有其他任何作用,所以,对于这个类,只要简单地把拷贝构造函数和赋值运算符屏蔽就好了:
class AutoMutex{
private:
const AutoMutex&
operator=(const AutoMutex&)=delete;
AutoMutex(const AutoMutex&)=delete;
mutex μ
public:
AutoMutex(mutex &t):mu(t){
mu.lock();
}
~AutoMutex(){
mu.unlock();
}
};
为了应对本宝宝的机智,小明又写出下面这段代码:
mutex mu;
void
mythread(){
AutoMutex t(mu);
AutoMutex t2(mu);
cout<<"hello world"<<endl;
}
int
main(){
for(int i=0;i<10;++i)
thread(mythread).detach();
system("pause");
}
不得不说,小明是很奸诈的~~~
一个互斥锁,对于一个线程来说,只有获取和没获取两种状态,而不存在获取两次这种状态。而不存在什么获取多次什么的状态。
我们先看一下,对于mutex,获取多次是个什么结果:
mutex mu;
void
mythread(){
mu.lock();
mu.lock();
cout<<"hello world"<<endl;
mu.unlock();
mu.unlock();
}
int
main(){
for(int i=0;i<10;++i)
thread(mythread).detach();
system("pause");
}
运行结果:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
请按任意键继续. . .
既然mutex本身就是这么设计的,我们还是不改的好~~~
猜想mutex这样设计是为了提供PV锁机制:
下面这段代码,不加任何互斥:
int
main(){
cout<<1<<endl;
thread([](){cout<<3<<endl;}).detach();
cout<<2<<endl;
thread([](){cout<<4<<endl;}).detach();
cout<<5<<endl;
}
输出结果为:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
1
3
2
54
完全没有顺序可言,但是如果加上一些互斥。
mutex mu;
int
main(){
cout<<1<<endl;
thread([](){cout<<3<<endl;mu.unlock();}).detach();
mu.lock();
cout<<2<<endl;
mu.lock();
thread([](){cout<<4<<endl;mu.unlock();}).detach();
mu.lock();
cout<<5<<endl;
mu.unlock();
}
此时的输出结果为:
C:\Users\SkyFire\Desktop>g++ temp.cpp -std=c++11
C:\Users\SkyFire\Desktop>a
1
2
3
4
5
Perfect!!!
这正是mutex为我们提供的特性,既然我们是管理mutex,我们就不该破坏这种特性。
于是~~~上面全是小明的错^_^。
这里实现的只是对mutex对象的管理,采用了禁止拷贝的方式,但是对其他对象的管理就不一定了,要根据对象的特性灵活管理。
常见的拷贝行为有:禁止拷贝(例如本类)、引用计数(例如上节的智能指针),但是要记住,如果实现了拷贝,一定要将所有元素全部拷贝。
EC笔记:第三部分:14、在资源管理类中小心Copying行为的更多相关文章
- Effective C++(14) 在资源管理类中小心copying行为
问题聚焦: 上一条款所告诉我们的智能指针,只适合与在堆中的资源,而并非所有资源都是在堆中的. 这时候,我们可能需要建立自己的资源管理类,那么建立自己的资源管理类时,需要注意什么呢?. ...
- Effective C++ -----条款14: 在资源管理类中小心copying行为
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为. 普遍而常见的RAII class copying行为是:抑制copying(使用私有继承 ...
- 【14】在资源管理类中小心copying行为
1.为什么要使用资源管理类? 资源管理类的思路就是,栈上的对象,封装堆上分配的资源,确保一定会释放资源.auto_ptr和shared_ptr就是资源管理类,行为上像指针. 2.auto_ptr和sh ...
- 条款14:在资源管理类中小心copying行为
请牢记: 1.复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为. 2.普遍常见的RAII class copying行为是:抑制copyin ...
- [Effective C++ --014]在资源管理类中小心copying行为
第一节 <背景> 条款13中讲到“资源取得的时机便是初始化时机”并由此引出“以对象管理资源”的概念.通常情况下使用std中的auto_ptr(智能指针)和tr1::shared_ptr(引 ...
- effective条款15,在资源管理类中小心copying行为
class A { private: int *p; void lock(){ cout << p << "is lock" << endl; ...
- Effective C++ 条款13/14 以对象管理资源 || 在资源管理类中小心拷贝行为
三.资源管理 资源就是一旦你使用了它,将来不用的时候必须归还系统.C++中最常用的资源就是动态内存分配.其实,资源还有 文件描述符.互斥器.图形界面中的字形.画刷.数据库连接.socket ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...
随机推荐
- 在你的ASP.NET MVC中使用查找功能
在程序中,使用查找功能是少之不了.今天在ASP.NET环境下演示一回. 在cshtml视图中,有三个文本框,让用户输入关键词,然后点击最右连的“搜索”铵钮,如果有结果将显示于下面. Html: 表格放 ...
- 后缀数组的倍增算法(Prefix Doubling)
后缀数组的倍增算法(Prefix Doubling) 文本内容除特殊注明外,均在知识共享署名-非商业性使用-相同方式共享 3.0协议下提供,附加条款亦可能应用. 最近在自学习BWT算法(Burrows ...
- Android—Service与Activity的交互
service-Android的四大组件之一.人称"后台服务"指其本身的运行并不依赖于用户可视的UI界面 实际开发中我们经常需要service和activity之间可以相互传递数据 ...
- (转载) RESTful API 设计指南
作者: 阮一峰 日期: 2014年5月22日 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). 因此,必须有一种统一的机制 ...
- Java中的进程与线程(总结篇)
详细文档: Java中的进程与线程.rar 474KB 1/7/2017 6:21:15 PM 概述: 几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进 ...
- Math.random()
Math.random() 日期时间函数(需要用变量调用):var b = new Date(); //获取当前时间b.getTime() //获取时间戳b.getFullYear() //获取年份b ...
- Android View.setId(int id) 用法
Android View.setId(int id) 用法 当要在代码中动态的添加View并且为其设置id时,如果直接用一个int值时,Studio会警告. 经过查询,动态设置id的方法有两种; 1. ...
- [转载]Google Guava官方教程(中文版)
原文链接 译文链接 译者: 沈义扬,罗立树,何一昕,武祖 校对:方腾飞 引言 Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] ...
- 在linux系统中安装VSCode(Visual Studio Code)
在linux系统中安装VSCode(Visual Studio Code) 1.从官网下载压缩包(话说下载下来解压就直接可以运行了咧,都不需要make) 访问Visual Studio Code官网 ...
- ★Kali信息收集~2.Whois :域名信息
Web地址:http://whois.chinaz.com/ | http://www.whois.net/ 软件参数:whois 常用命令:whois 域名 (重点看whois server和R ...