`
RednaxelaFX
  • 浏览: 3014870 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

把lock的意思给弄混了 T T

    博客分类:
  • C#
阅读更多
悲剧啊……前几天有个同学不停问我Java里的同步问题,今天写C#的时候却不小心把lock的语义给弄混了。诶,才回答过的问题换个马甲就把我给绊住了 T T

下午写代码的时候,对某个库的实现有点疑问,就钻进去看了下。然后看到一个方法A里是用lock语句包围的,获取了一个锁,其中调用了一个辅助方法B;B也是被lock语句包围的,要获取了同一个锁。我就纳闷这代码是怎么跑得通的,两个方法要同时获取同一个锁不是阻塞了么?
然后我发现我是把lock跟跟线程无关的简单的flag给弄混了 T T

关键是,lock语句、Mutex和Semaphore都是以线程为单位来获取/释放的,而不是以方法之类的为单位。如果一个方法已经获取了某个锁,它调用另一个方法也要获取同一个锁,那么完全没问题,因为方法调用是在同一个线程上的。

把我看到的代码简化一下,状况如下例所示:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    readonly object LockObject;
    T[] _data;
    
    public ArrayWrapper() {
        LockObject = new object();
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        lock (LockObject) {
            Console.WriteLine("acquired lock in TrySetValue");
            var data = _data;
            
            if (index < 0) return;
            if (index >= data.Length) {
                data = PromoteData(data.Length, index + 1);
            }
            data[index] = value;
        }
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        lock (LockObject) {
            Console.WriteLine("acquired lock in PromoteData");
            if (_data.Length == oldLen) {
                var data = new T[newLen];
                _data.CopyTo(data, 0);
                _data = data;
            }
            return _data;
        }
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}


换成Mutex也一样:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    readonly Mutex _mutex;
    T[] _data;
    
    public ArrayWrapper() {
        _mutex = new Mutex();
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        _mutex.WaitOne();
        Console.WriteLine("acquired mutex in TrySetValue");
        var data = _data;
        
        if (index < 0) return;
        if (index >= data.Length) {
            data = PromoteData(data.Length, index + 1);
        }
        data[index] = value;
        _mutex.ReleaseMutex();
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        _mutex.WaitOne();
        Console.WriteLine("acquired mutex in PromoteData");
        
        T[] data = _data;
        if (_data.Length == oldLen) {
            data = new T[newLen];
            _data.CopyTo(data, 0);
            _data = data;
        }
        _mutex.ReleaseMutex();
        return data;
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}


我是想像成类似这样了……(嗯我知道Interlocked.Exchange()和Interlocked.CompareExchange()不一样,只是说类似)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

static class Program {
    static void Main(string[] args) {
        var foo = new ArrayWrapper<string>();
        foo.TrySetValue(2, "Charlie");
        foo.TrySetValue(0, "Alpha");
        foreach (var str in foo) {
            Console.WriteLine(str);
        }
    }
}

class ArrayWrapper<T> : IEnumerable<T>, IEnumerable {
    int _flag;
    T[] _data;
    
    public ArrayWrapper() {
        _flag = 0;
        _data = new T[0];
    }
    
    public void TrySetValue(int index, T value) {
        var done = false;
        while (!done) { // spin on the lock
            Console.WriteLine("trying to acquire lock in TrySetValue");
            if (0 == Interlocked.Exchange(ref _flag, 1)) {
                Console.WriteLine("acquired mutex in TrySetValue");
                var data = _data;
                
                if (index < 0) return;
                if (index >= data.Length) {
                    data = PromoteData(data.Length, index + 1);
                }
                data[index] = value;
                
                done = true;
                Interlocked.Exchange(ref _flag, 0);
            }
            
            if (!done) Thread.Sleep(500);
        }
    }
    
    private T[] PromoteData(int oldLen, int newLen) {
        Debug.Assert(oldLen != newLen);
        
        T[] data = null;
        var done = false;
        while (!done) { // spin on the lock
            Console.WriteLine("trying to acquire lock in PromoteData");
            if (0 == Interlocked.Exchange(ref _flag, 1)) {
                Console.WriteLine("acquired mutex in PromoteData");
                
                data = _data;
                if (_data.Length == oldLen) {
                    data = new T[newLen];
                    _data.CopyTo(data, 0);
                    _data = data;
                }
                
                done = true;
                Interlocked.Exchange(ref _flag, 0);
            }
            
            if (!done) Thread.Sleep(500);
        }
        return data;
    }
    
    public IEnumerator<T> GetEnumerator() {
        var data = _data;
        foreach (var item in data) {
            yield return item;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

这样写的话,“获取”一个锁(也就是把_flag置为1,并且_flag原来的值为0)就不是以线程为单位。于是就死锁了。正常用lock、Mutex、Semaphore不会遇到这种问题……

就算Monitor.Enter里面有用到InterlockedCompareExchange()……Monitor.Enter并不是简单的把SyncBlock里的值在0和1间切换,而是还涉及到线程身份。我一下把这个给忘了,赶紧记下来 TvT
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics