Skip to content

Instantly share code, notes, and snippets.

@JeffreyZhao
Created January 27, 2014 07:56
Show Gist options
  • Save JeffreyZhao/8644613 to your computer and use it in GitHub Desktop.
Save JeffreyZhao/8644613 to your computer and use it in GitHub Desktop.
// 其实这题可以写的很简单,它的关键根本不是CAS操作。
// 使用一个标志(flag也好,status判断也罢)来避免Start和Dispose重入是很容易
// 想到的手段,很多同学也这么做了。大部分同学遇到的问题是:假如StartCore已经
// 启动,那么如何让Dispose方法等待其完成。有些同学用while (true),有些同学用
// 锁,但都避免不了让Dispose线程等待Start线程。
// 这里“避免等待”的关键在于要跳出思维界限:谁说DisposeCore方法一定要在Dispose
// 方法里执行的?我们就不能在Start里销毁对象吗?
public class FineLockCalculator : CalculatorBase {
private enum Status {
NotStarted,
Starting,
Started,
Disposed
}
private readonly object _gate = new object();
private Status _status = Status.NotStarted;
public virtual void Start() {
lock (_gate) {
if (_status != Status.NotStarted)
return;
_status = Status.Starting;
}
StartCore();
lock (_gate) {
if (_status == Status.Starting) {
_status = Status.Started;
return;
}
}
DisposeCore();
}
public void Dispose() {
lock (_gate) {
var origStatus = _status;
_status = Status.Disposed;
if (origStatus != Status.Started)
return;
}
DisposeCore();
}
}
// 在Dispose方法中,我们对_status进行标记,但只在确定Started的情况下才
// 调用DisposeCore。同理,在Start方法中假如发现_status已经变成了Disposed,
// 则原地调用DisposeCore销毁对象。使用这种方法,Dispose方法完全不需要做
// 任何等待,整体等待时间极短。
// 清晰起见,上述代码没有使用任何CAS操作,只是对于_status读写加以简单保护。
// 假如需要的话,我们也可以把三个lock轻松修改为CAS操作,但这只是起到锦上
// 添花的作用。
// 有些同学一上来就搞CAS,然后发现问题之后只是简单的修补,于是代码越变越
// 复杂。理清思路,划分好边界,处理好边界上的竞争关系,其实逻辑说到底就是
// 这么简单.
@JeffreyZhao
Copy link
Author

@fengyj: 现在Start不是启动服务么?我也没说计算的动作在Start里,Start可以只是发起监听。你都知道Start可以立即返回而不用等待StartCore结束,那还有什么问题?非要我把做法都告诉你,例如:“注意Start里可以调用DisposeCore哦”,才叫清楚?

@yuanw: 会有什么问题?

Copy link

ghost commented Jan 27, 2014

受教了,只想到多线程在lock那儿的等待耗时,没注意到Dispose线程等待Start线程的问题,考虑问题还是不够全面。。。

@airekans
Copy link

嗯,老赵的状态转换和我的是类似的,不过省了一个DisposingDispose代码也省了不少,易读了不少。
顺便贴一下自己的代码,哈哈哈哈 https://gist.github.com/JeffreyZhao/8630308/#comment-993947

不过我是比较好奇这个类具体的计算函数(我猜会有个Calculate函数吧?)是怎么用的,也就是客户代码是怎么个乱序调StartDispose的。

@JeffreyZhao
Copy link
Author

@airekans

没有Calculate函数。StartCore里面是开始监听一个数据源,并把计算结果表达出来。一个Calculator对象可以被多个线程持有,在某一个阶段需要开启,但内部显然只能调用StartCore一次。至于Dispose,的确只有一个线程会调用,但在.NET里IDisposable接口本身的协议是要求可以多次调用相当于调用一次。换个高大上的说法,是“幂等”的,Start也是。

@jinsikui
Copy link

貌似没发现bug,不爽

@dawnbreaks
Copy link

public class BigLockCalculator extends CalculatorBase {

    private interface Status {
        int NotStarted =1;
        int Starting =2;
        int Started =3;
        int Disposed =4;
    }

    private AtomicInteger _status = new AtomicInteger(Status.NotStarted);
    private AtomicInteger _disposingFlag = new AtomicInteger(0);
    private AtomicInteger _startingFlag = new AtomicInteger(0);

    public override void Start() {
        if(_status.get() != Status.NotStarted ) {
            return;
        } 

        if( _startingFlag.compareAndSwap(0,1)) {

            try {
                if(_status.compareAndSwap(Status.NotStarted, Status.Starting)) {

                    StartCore();

                    if(!_status.compareAndSwap(Status.Starting, Status.Started)) { 
                        if(_status.get() == Status.Disposed ) { 
                            DisposeCore();
                            return;
                        } 
                    }
                }

            }finally {
                _startingFlag.compareAndSwap(1,0);
            }
        }
    }

    public override void Dispose() {

        if(_status.get() == Status.Disposed) {
            return;
        } 

        if( _disposingFlag.compareAndSwap(0,1)) {

            try {

                while(true) {

                    int currentStatus =_status.get();

                    if(currentStatus == Status.Disposed) {
                        return;
                    }

                    if(currentStatus == Status.NotStarted && _status.compareAndSwap(currentStatus, Status.Disposed )) {
                        return;
                    }else if(currentStatus == Status.Starting && _status.compareAndSwap(currentStatus, Status.Disposed )){
                        return;
                    }else if(currentStatus == Status.Started && _status.compareAndSwap(currentStatus, Status.Disposed )){
                        DisposeCore();
                    }

                }

            }finally {
                _disposingFlag.compareAndSwap(1,0);
            }
        }
    }
}

@JeffreyZhao 帮忙review下纯CAS版本,无锁。

@ivenxu
Copy link

ivenxu commented Jan 27, 2014

@JeffreyZhao 弱弱的问一句,在Start()的最后一句为什么是DisposeCore()而不是Dispose()呢?在这里直接调用DisposeCore()是不是意味着,有个前提的假设是基类里实现的DisposeCore()可以被多次重复调用呢?

@JeffreyZhao
Copy link
Author

@dawnbreaks: 没仔细看,但为什么会那么复杂,按照我的代码改写成CAS的,应该十分简单才对。

@ivenxu: 当然应该调用DisposeCore,再仔细看一下代码,DisposeCore不会被重复调用。

@MikeBL
Copy link

MikeBL commented Jan 27, 2014

Nice approach. The key point here is that thinking outside of box, let DisposeCore being called from Start().

@ivenxu
Copy link

ivenxu commented Jan 28, 2014

@JeffreyZhao, 仔细看了,DisposeCore()确实不大可能被重复调用.再弱弱的多问一句,DisposeCore()在Start()中在什么样的场景才会被调用?我实在想不出来,因为在我看来在Start()里面执行完_status = Status.Started;就return;了。

@beerbubble
Copy link

@ivenxu Start()里面的DisposeCore()还是有可能会被调用的,比如正在执行StartCore()的时候调用了Dispose(),那么_status = Status.Disposed,然后StartCore()执行完毕后会调用DisposeCore(). 以上只是个人粗浅的理解。

@yhliaoluan
Copy link

又学了一手,太机智了。
不过仔细想想,这样对于调用者来说太trick了吧。比如我发现dispose结束了,心想太好了可以拔电源/拔网线了,但其实DisposeCore可能还没开始呢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment