1. 场景

上一节实现了智能指针,其中的拷贝构造函数和赋值运算符是通过增加/减少指针的引用计数来操作的。但是如果是管理一个独占资源呢?我们希望在一个资源使用时被锁定,在使用完毕后被释放。

#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中断的结果。

为了避免这种情况,我们使用资源管理类。

  1. 简单的实现

一个简单的实现:

class AutoMutex{

private:

mutex &mu;

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

请按任意键继续. . .

  1. 问题

但是,如果出现一些比较调皮的程序员(暂定为小明吧)。

调皮的小明写出了如下的代码:

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 &mu;

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行为的更多相关文章

  1. Effective C++(14) 在资源管理类中小心copying行为

    问题聚焦:     上一条款所告诉我们的智能指针,只适合与在堆中的资源,而并非所有资源都是在堆中的.     这时候,我们可能需要建立自己的资源管理类,那么建立自己的资源管理类时,需要注意什么呢?. ...

  2. Effective C++ -----条款14: 在资源管理类中小心copying行为

    复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为. 普遍而常见的RAII class copying行为是:抑制copying(使用私有继承 ...

  3. 【14】在资源管理类中小心copying行为

    1.为什么要使用资源管理类? 资源管理类的思路就是,栈上的对象,封装堆上分配的资源,确保一定会释放资源.auto_ptr和shared_ptr就是资源管理类,行为上像指针. 2.auto_ptr和sh ...

  4. 条款14:在资源管理类中小心copying行为

    请牢记: 1.复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为. 2.普遍常见的RAII class copying行为是:抑制copyin ...

  5. [Effective C++ --014]在资源管理类中小心copying行为

    第一节 <背景> 条款13中讲到“资源取得的时机便是初始化时机”并由此引出“以对象管理资源”的概念.通常情况下使用std中的auto_ptr(智能指针)和tr1::shared_ptr(引 ...

  6. effective条款15,在资源管理类中小心copying行为

    class A { private: int *p; void lock(){ cout << p << "is lock" << endl; ...

  7. Effective C++ 条款13/14 以对象管理资源 || 在资源管理类中小心拷贝行为

    三.资源管理       资源就是一旦你使用了它,将来不用的时候必须归还系统.C++中最常用的资源就是动态内存分配.其实,资源还有 文件描述符.互斥器.图形界面中的字形.画刷.数据库连接.socket ...

  8. 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问

    1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...

  9. 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎

    1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...

随机推荐

  1. MementoPattern(备忘录模式)

    /** * 备忘录模式 * @author TMAC-J * 用于存储bean的状态 */ public class MementoPattern { public class Memento{ pr ...

  2. Oracle第一步

    Oracle 启动数据库 Startup [NOMOUNT|MOUNT|OPEN|FORCE] [restrict] [pfile=filename] 启动实例,加载数据库,启动数据库 oRACLE关 ...

  3. BPM流程中心解决方案分享

    一.需求分析 在过去办公自动化的浪潮中,很多企业已经实施了OA流程,但随着客户的发展和对流程管理的越来越重视, 客户对流程应用需求越来越深 入,您可能面临以下需求: 1.流程功能不能满足需求,包括流程 ...

  4. BPM助力企业数字化转型

    自九十年代末,流程管理开始引入国内,至今已经有20多年的历史了,由最初的部门级应用向企业级应用转变,大家的认知也经历了一系列的发展变化.不同阶段的信息化水平对企业的流程以及BPM平台也提出了不同的需求 ...

  5. Lucene4.4.0 开发之排序

    排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最 ...

  6. 如何区别数据库删除语句drop与delete与truncate?

    1.delete:删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行) 删除某一行:delete from 数据表名称 where 列名称=值: 删除所有行:delete*fro ...

  7. windows下mongodb配置

    打开cmd(windows键+r输入cmd)命令行,进入D:\mongodb\bin目录(如图先输入d:进入d盘然后输入cd d:\mongodb\bin), 输入如下的命令启动mongodb服务: ...

  8. [原创]ubuntu16.04LTS使用细节

    如何给自己安装的应用创建桌面图标 拿php开发神器phpstorm为例,找到可执行文件所在路径. 这里是/home/haive/PhpStorm/bin/phpstorm.sh 打开dash,搜索&q ...

  9. Membership三步曲之进阶篇 - 深入剖析Provider Model

    Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...

  10. 从 Everything 到 Listary,自 Launch 归 Wox

    人生即在于体验,而体验源于去尝试,去折腾,去改变,去塑新.要知道:"过一个平凡无趣的人生实在太容易了,你可以不读书,不冒险,不运动,不写作,不外出,不折腾--但是,人生最后悔的事情就是:我本 ...