在实际的软件编程中,经常会遇到资源的争用,比如下面的例子:

class Counter
{private:int value;public:Counter(int c) { value = c; }int GetAndIncrement(){int temp = value;      //进入危险区        value = temp +1;       //离开危险区return value;}}

这种实现在单线程系统中能够正常工作,但是在多线程系统则有可能出错。比如有2个线程,初始状态value=0。第一个线程运行完第9行,这时temp=0。突然一个中断来了,切换到第二个线程运行了,第二个线程运行完第9行也是temp=0,然后执行第10行赋值value=1。然后回到第一个线程继续运行第10行对value进行写覆盖,结果value=1.而正确的情况应该是value=2了。
      为什么会产生这样的情况呢?这时因为两个线程同时对一个资源value进行争用产生了冲突。为了避免上述情况,我们可以将这两行置入临界区内:某个时刻内仅能被一个线程执行的代码段。从而实现互斥。对Counter类的增加对临界区的互斥访问:

 class Counter{private:int value;lock lock;public:Counter(int c) { value = c; }int GetAndIncrement(){lock.lock();//获取锁int temp = value; //进入临界区 value = temp +1; //离开临界区lock.unlock();//释放锁return value;}}

通过在程序中为了使用Lock域来保证对象的互斥特性,必须对称的调用lock()和unlock()。需要满足如下条件:
     1. 一个临界区之和一个lock对关联。
     2. 线程进入临界区前调用lock()。
     3. 线程离开临界区后调用unlock().
      编程的框架如下:
      lock()
      临界区
      unlock()
       打个不是十分妥帖的比喻,就像是有一个仓库资源,但是有多个人想去仓库做点事情。这时候仓库只需要一把锁(锁多了纯粹是浪费^_^),初始状态仓库上的锁是打开的。每个人进去之前先把锁锁住(避免别的人进来),然后自己在仓库里捣弄,离开时再把仓库的锁打开,让别人可以进来。
      接下来更加深入的是如何实现互斥锁呢?也就是lock()和unlock()方法。

class Lock
{public: virtual void lock() = 0; //进入临界区前 virtual void unlock() = 0; //离开临界区后
}

互斥锁需要满足三个条件:
       互斥   不同线程的临界区没有重叠
       无死锁  如果一个线程正在尝试获得一个锁,那么总会成功地获得这个锁。若线程A调用lock()但是无法获得锁,则一定存在其他线程正在无穷次地执行临界区。
       无饥饿  每一个试图获得锁的线程最终都能成功。
       首先看双线程的互斥,首先从两个存在不足(如果大家能不看后面的分析也能知道哪里不足就更厉害了^_^),但十分有趣的锁算法说起:
  LockOne类
       这个类有一个标志数组flag,继续来个比喻,这个flag就相当于一个旗帜。LockOne类遵循这样的协议:
      1. 如果线程想进入临界区,首先把自己的旗帜升起来(flag相应位置1),表示感兴趣。然后等对方的旗帜降下来就可以进入临界区了。
       2. 如果线程离开临界区,则把自己的旗帜降下来。

  class LockOne: public Lock{private:bool flag[2];public:void lock(){int i = ThreadID.get();int j = 1-i;flag[i] = true;while(flag[j]);}void unlock(){int i = ThreadID.get();flag[i] = false;}}

LockOne类的协议看起来挺朴实的,但是存在一个问题:当两个线程都把旗帜升起来,然后等待对方的旗帜降下来就会出现死锁的状态(两个线程都在那傻乎乎的等待对方的旗帜降下来,直到天荒地老:))

 LockTwo类
       观察LockOne类存在的问题,就是在两个线程同时升起旗帜的时候,需要有一个线程妥协吧,这样就需要指定一个牺牲品,因此LockTwo类横空出世。

 class LockTwo: public Lock{private:int victim;public:void lock(){int i = ThreadID.get();victim = i;                  //让别人先走,暂时牺牲自己while(victim == i);}void unlock(){]}

当两个线程进行竞争的时候,总有一个牺牲品(较晚对victim赋值的线程),因此可以避免死锁。但是,当没有竞争的时候就杯具了,如果只有一个线程想进入临界区,那么牺牲品一直是自己,直到等待别人来替换自己才行。

 Perterson锁
       通过上面两个类可以发现,LockOne类适合没有竞争的场景,LockTwo类适合有竞争的场景。那么将LockOne类和LockTwo类结合起来,就可以构造出一种很好的锁算法。该算法无疑是最简洁、最完美的双线程互斥算法,按照其发明者的名字被命名为“Peterson算法”。

class Peterson: public Lock
{private:bool flag[2];int victim;public:void lock(){int i = ThreadID.get();int j = 1-i;flag[i] = true;victim = i;while(flag[j] && victim==i);}void unlock(){int i = ThreadID.get();flag[i] = false;}
}

Perterson锁是满足互斥特性的。通过反证法来说明,如果两个线程都想进入临界区,但是都成功进入了。因为两个线程都想进入,则说明flag对应位均为1,然后因为都能lock()成功,说明victim均不是自己。这和victim是其中之一矛盾。

但是,实际中线程不可能只有2个,接下来需要看看支持n线程的互斥协议。

Barkey锁

有一种协议称为Bakery锁,是一种最简单也最为人们锁熟知的n线程锁算法。下面看看到底是神马情况。思想很简单,还是打个简单的比喻来说明器协议:
       1. 每个线程想进入临界区之前都会升起自己的旗帜,并得到一个序号。然后升起旗帜的线程中序号最小的线程才能进入临界区。 
       2. 每个线程离开临界区的时候降下自己的旗帜。

class Bakery: public Lock
{private: bool flag[];Label label[];public:Bakery (int n){flag = new bool[n];label = new Label[n];for(int i=0; i<n; i++){flag[i] = flase;label[i]  =  0;}void Lock(){int i = ThreadID.get();flag[i] = true;               label[i] = max(label[0], ..., label[n-1]) +1;while((exist k!=i)(flag[k] && (label[k],k)<<(label[i], i))}void unlock(){flag[TheadID.get()] = false;}}
}

首先,Barkey算法是无死锁的。因为正在等待的线程中(类似于所有升起旗帜flag的线程中),必定存在一个最小的序号label。该线程可以进入临界区。
       其次,Barkey算法是先来先服务的。因此先来的线程,分到的label比较小。
       最后,Barkey算法是互斥的。如果两个线程同时位于临界区,则两个线程都已经升起旗帜,同时label都是最小的,矛盾。
       很重要的点是要实现一个n线程的互斥锁,必须至少使用n个存储单元。因为若此刻有某个线程正在临界区内,而锁的状态却与一个没有线程在临界区或正在临界区的全局状态相符,则状态不一致。即每个线程共有2个状态,则n个线程共有2^n个状态,共需要n个存储器记录全局状态。

深入理解互斥锁的实现相关推荐

  1. linux互斥锁和条件变量,如何理解互斥锁和条件变量?

    下面的代码出自<Unix/Linux编程实践教程>,作用是用两个线程分别统计两个文件的单词的数目,并在主线程中计算总数.下面是运行截图: 但是看了半天还是难以理解下面代码中的加锁.解锁以及 ...

  2. python之互斥锁

    python之互斥锁 1.互斥锁的概念 互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作.  [对共享数据进行锁定可以理解为全局变量] 注意: 互斥锁是多个线程一起去抢,抢到锁的线程先执 ...

  3. java的尝试性问题_Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和有序性的问题,那么还有一个原子性问题咱们还没解决.在第一篇文章01并发编程的Bug源头当中,讲到了把一个或者多 ...

  4. python3 一 线程与互斥锁详解

    什么是线程? 可以简单理解为同一进程中有多个计数器,每个线程的执行时间不确定,而每个进程的时间片相等,线程是操作系统调度执行的最小单位. 线程的创建步骤 Import threading # 导入模块 ...

  5. l2tp连接尝试失败 因为安全层在初始化_线程安全互斥锁

    1认知 线程安全机制: 由于线程它是共享进程里面所有的资源,当然这里面就会包括虚拟内存里面所有的东西(包含全局变量,堆内存,映射的内存及程序段落等),它也继承了进程当中所有的资源(文件描述符,信号资源 ...

  6. linux互斥锁和PV原语

    刚接触linux互斥锁的时候可能会比较抽象,所以本文想要用PV原语来更加具体的理解linux互斥锁.如若有误,烦请指出,不甚感激! 由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都 ...

  7. 互斥锁深度理解与使用

    大家好,我是易安! 我们知道一个或者多个操作在CPU执行的过程中不被中断的特性,称为"原子性".理解这个特性有助于你分析并发编程Bug出现的原因,例如利用它可以分析出long型变量 ...

  8. iOS开发中自旋和互斥锁的理解以及所有锁的性能比较

    补充: 可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的.苹果在新系统中已经优化了 pthread_mutex 的性能,所以它 ...

  9. 深入理解 golang 的互斥锁

    How to implement Golang Mutex golang 是如何实现互斥锁的 在开始之前,我们需要知道锁实现的几种方式. # 信号量 操作系统中有 P 和 V 操作.P 操作是将信号量 ...

最新文章

  1. UVA12325Zombie's Treasure Chest 宝箱
  2. Kubernetes资源创建yml语法
  3. oracle数据块调用存储过程,VC调用存储过程的通用方法(ORACLE篇)
  4. html5 密码框明文,elementUI的密码框的密文和明文
  5. WIN7开启WIFI
  6. PHP 5.6 中 Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future
  7. c# 多线程界面卡顿_优化electron客户端卡顿的几种方案
  8. Google 编程之夏:海量优质项目,丰厚报酬,你竟然还不知道?
  9. 转:Python yield 使用浅析
  10. 攻略:简易病毒制作(Windows)
  11. 鸿蒙系统手机开机,华为手机鸿蒙系统 OS 2.0 开机界面演示
  12. Excel画饼图(立体的哦)
  13. 2019年长安杯 第一届电子数据取证竞赛 wp
  14. 【无标题】MobaXterm远程连接服务器跑深度学习
  15. [数据结构]递归树:借助树求解递归算法的时间复杂度
  16. (3)verilog与VHDL两种语言编写二分频
  17. MICRO和GO-MICRO
  18. linux系统安装mysql详细教程
  19. 量化分析师的Python日记【Q Quant兵器谱之二叉树】
  20. 顶刊实证复现:扶贫改革试验区的经济增长效应及政策有效性评估!思路梳理+全数据源+python代码

热门文章

  1. 哈师大计算机学院2016级新生,【通知公告】哈尔滨师范大学2016—2017学年度国家励志奖学金获奖学生初审名单公示...
  2. linux quota硬盘,Linux系统中quota磁盘命令的相关使用解析
  3. qt连接mysql数据库 mac_Mac系统下Qt 4.8编译连接数据库(Oracle,MySql)
  4. Java-JUC-彻底搞懂JUC
  5. CD19药物|适应症|市场销售-上市药品前景分析
  6. C语言——整型整除,浮点数整除
  7. 常见数据类型的散列函数
  8. 中位数的应用—士兵站队问题
  9. 解决办法: Vue cross-env NODE_ENV=production webpack --progress --hide-module
  10. windows重装系统