zoukankan      html  css  js  c++  java
  • leetcode1116 多信号量嵌套控制下的DCL检查

      先吐槽一下网上对原子性的解释,总是说原子性是不可分割的。

      那只是在单核心下的语义,单核心下不可分割的操作意味着执行过程中不会有其它线程执行,从而导致变量污染,也就是原子性操作涉及的共享变量是安全的。

      所以多线程下原子性的语义也应该是 :原子性操作涉及的共享变量是安全的,不会有其它线程修改。

      也就是说保证原子性,是保证“某段代码”以及“对所有这段代码中牵扯到的共享变量的操作”是线程排他的。

      在单线程环境下存在数据依赖或者控制依赖的一段指令,多线程下我们必须保证其原子性。原因很简单,如果它们不是原子的,在它们执行的间隙,其它线程改变共享变量的值会导致变量污染。

      但当外部控制是信号量而不是锁时,无法保证内部逻辑的原子性。所以我们进行完一系列控制后,在真正执行任务前需要返回来判断外部控制是否还是有效的,并在内部控制保证任务的原子性,这便是DCL的思路。

      题目如上,题目不难,是一个通过信号量通信的典型例子。

      题目中几个点比较具有代表意义:

      1. 对于共享变量 n 的累减与 current 的累加是一个典型的基于共享变量当前值计算下一步值的操作,通常情况下是一个读操作与一个写操作的组合。因为这两个操作在单线程环境下存在数据依赖,所以在多线程情况下我们应该保证其原子性,防止在单线程的两个操作之间,其它线程修改了共享变量的值造成变量污染。

      但是在本题中,每个线程需要自己的信号量放行后才可以执行共享变量 n 的累减与 current 的累加操作,且同一时间只有一个信号量是 true ,所以我们在保证线程对信号量的访问正确性的情况下,信号量等于是共享变量 n 的累减与 current 的累加的锁,同一时间只有一个线程可以执行,天然的保证了其原子性。

      2. n 与 三个信号量共同构成了线程执行任务的条件。

      当我们遇到类似的结构时:

      while( 条件1 ){

        while( 条件2 ){

          执行任务;

        }

      }

      任务的执行需要多个共享变量放行,我们需要尤其注意。

      在满足了外层条件并等待在内层条件满足的时间段中,外层条件很可能被其它线程改变。因此在满足内层条件后我们一定要对外层条件再进行一次检查。除非内层条件的等待与外层条件的修改是互斥的,但代价非常大。因为我们要互斥的,不是 条件1 的写操作与 条件2 的读操作,而是 条件1 的写操作与等待条件2直到其为真。

      比如:

    int lock1=10;
    int lock2=0;
    private synchronized void setLock1(int i){
        lock1=i;  
    }
    while(lock1!=0){
        synchronized(this){
            while(lock2==0){
                等待;
            }
    开启lock3; } }

      使 lock1 的写操作与等待在 lock2 上的所有操作共用一把锁。逻辑上很合理,一个线程基于 lock1==1 做事,那么我做完之前,谁都不允许去修改 lock1 。

      如果说,所有线程都只使用这两把锁,这样做没有什么问题。但如果 lock2 控制的任务牵扯到与其它线程的协作的话,情况就复杂了起来。

      比如此时还有一个线程:

    while(lock1!=0){
        synchronized(this){
            while(lock3==0){
                等待;
            }
    开启 lock2;
    } }

      需要锁的顺序为: lock1-->lock3-->unlock2 ,而上一个线程的任务需要锁的顺序为:lock1-->lock2-->unlock3。

      lock3开启lock2关闭的情况下,第一个线程先获取lock1,则整个程序便死锁了,因此我们需要更细粒度的互斥关系。

      lock1 不应该是锁,而应该是信号量,它代表的是线程可用资源而不是保证线程互斥,那么多个线程可以进入 lock1 控制的区域。但是这样依然不够,lock3-->unlock2 与

    lock2-->unlock3 存在循环依赖,依然会造成死锁。所以我们必须让进入 lock2 与进入 lock3 也是互斥的。

      而在 lock1 控制的区域内部,执行 lock2 加锁区域 与 执行 lock3 加锁区域是互斥的,而且只有在这两个区域内可以进行 lock1 的修改操作,那么不管进入 lock2 还是进入 lock3,再进行 lock1 的取值,其结果都将是线程安全的。

    class ZeroEvenOdd {
       //待打印数据个数
    private volatile int n;
       //打印 0 的信号量
    private volatile boolean isZero=true;
       //打印 偶数 的信号量
    private volatile boolean isEven=false;
       //打印 奇数 的信号量
    private volatile boolean isOdd=false;
       //当前打印到的数
    private volatile int current=0; public ZeroEvenOdd(int n) { this.n = n; } // printNumber.accept(x) outputs "x", where x is an integer. public final void zero(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isZero){ if(n==0){ return; } Thread.yield(); } if(n==0){ break; } printNumber.accept(0); isZero=false; if((current&1)==0){ isOdd=true; }else{ isEven=true; } } } public final void even(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isEven){ if(n==0){ return; } Thread.yield(); } current=current+1; printNumber.accept(current); n--; isEven=false; isZero=true; } } public final void odd(IntConsumer printNumber) throws InterruptedException { while(n!=0){ while(!isOdd){ if(n==0){ return; } Thread.yield(); } current=current+1; printNumber.accept(current); n--; isOdd=false; isZero=true; } } }
  • 相关阅读:
    pig实战 pig常用语法总结,教你快速入门——算法篇
    本科生码农应该会的6种基本排序算法(《数据结构与算法》)
    java 大块内存做数据缓存 大数据的高效收发
    一键安装zookeeper脚本制作篇 相关经验浅谈
    C语言第01次作业顺序、分支结构
    C语言 第三次作业函数
    hashmap笔记
    ArrayList排序与对象的序列化
    插入排序笔记(MIT算法导论课程)
    java解析四则运算表达式
  • 原文地址:https://www.cnblogs.com/niuyourou/p/12439643.html
Copyright © 2011-2022 走看看