profile for Macon_Cao at Stack Overflow, Q&A for professional and enthusiast programmers

看看这个Lock可不可靠

 

由于业务的需要,设计了一个Lock。

这个Lock的设计要求如下:

  1. 数据被多线程访问
  2. 对数据的访问分为读和写
  3. 当任一线程读数据时,其它线程不能写数据
  4. 当任一线程写数据时,其它线程不能写数据,其它线程不能读数据
  5. 由于读数据的频率远远高于写数据的频率,所以读数据线程的优先级更高
  6. 不允许死锁的情况发生

这里实际上要求的是读和写的互斥,写和写之间的互斥,但读与读之间并不互斥。显然,不能用一个简单的锁来搞定。例如下面的代码

class DataService<T>
    {
        List<T> mCache = new List<T>();
        private object mLockObject = new object();

        public List<T> Search(Predicate<T> predicate)
        {
            lock (mLockObject)
            {
                return mCache.FindAll(predicate);
            }
        }

        public void Upate(int key)
        {
            lock (mLockObject)
            { 
                //somemethod to update mCache with key
            }
        }
    }

这段代码确实是实现了读写互斥,写与写的互斥,但是读与读之间也发生了互斥。由于读的频率很高,所以读与读之间的互斥会直接影响到系统的性能。

为此,我设计了2个Lock

private object mWriteLock = new object();
            private object mReadLock = new object();

 

写与写的互斥

mWriteLock用于独占写入,但它仅仅控制的是写操作,对读操作没有影响。

public void LockToWrite(Action action)
            {
                lock (mWriteLock)
                {
                    action();
                }
            }

 

读与读之间不互斥

mReadLock用于读操作,但它并不直接锁定读操作本身,而是锁定读操作的计数。所以,和mReadLock一同工作的还有一个数据

private int mReadSeed = 0;

 

mReadLock实际上是用于锁定mReadSeed的读写。

private void IncrementReadSeeds()
            {
                lock (mReadLock)
                    mReadSeed++;
            }
            private void DecrementReadSeeds()
            {
                lock (mReadLock)
                    mReadSeed--;
            }

 

当读操作发生时,调用以上方法

public T LockToRead<T>(Func<T> action)
            {
                try
                {
                    IncrementReadSeeds();
                        
                    return action();
                }
                finally
                {
                    DecrementReadSeeds();
                }
            }

在读操作中,由于只是锁定的mReadSeeds的运算,而不是锁定的action运算,所以即便是action的运行时间会很长,也不会阻止其它线程的读操作。

其实这里暴露了一个问题,因为锁定的只是所谓的读操作,而锁定的数据却根本没有提及,即没有办法从代码上保证,action就不会对真正要保护的资源进行写操作。还好,这个类只会作为DataService类中的一个子类存在,这样就可以通过约定来解决上面的顾虑(目前水平有限,也只能出此下策)

 

读与写之间的互斥

读与写之间的互斥,需要将两个锁关联起来。这段代码很关键。

public void WaitToWrite()
            {
                while (true)
                {
                    lock (mReadLock)
                    {
                        if (mReadSeed == 0)
                        {
                            mCanRead = false;
                            break;
                        }
                    }
                    Thread.Sleep(1);
                }
            }

 

在WaitToWrite方法中,使用了mReadLock,当mReadSeed==0是,将mCanRead标记为false。

在LockToRead方法中,我们可以看到,只有当所有的读操作都完成后,mReadSeed才可能为0。而当所有的读操作完成后,WaitToWrite方法将mCanRead置为false。而这个操作受到了mReadLock的保护。

于是读的代码要稍作调整

public T LockToRead<T>(Func<T> action)
            {
                try
                {
                    while (true)
                    {
                        WaitToRead();
                        IncrementReadSeeds();
                        if (!mCanRead)//因为IncrementReadSeeds和WaitToWrite都使用了mReadLock,所以我认为这里读取mCanRead是安全的,这是这个算法的关键,不知道大家是否这么看?
                        {
                            DecrementReadSeeds();
                            continue;
                        }
                        else
                            break;
                    }

                    return action();
                }
                finally
                {
                    DecrementReadSeeds();
                }
            }

 

在上面的代码中调用了WaitToRead()方法,它实际上是用来判断当前的读操作是否被允许的第一道关卡。

WaitToWrite同样被置于mWriteLock的保护之下,那同样意味着mCanRead=false操作受到了mWriteLock的保护。写的代码调整为

public T LockToWrite<T>(Func<T> action)
            {
                lock (mWriteLock)
                {
                    WaitToWrite();//等待读操作完成

                    try
                    {
                        return action();
                    }
                    finally
                    {
                        mCanRead = true;//将允许读的操作置为true
                    }
                }
            }

是否会出现读写互锁

我认为问题的关键在于mReadSeeds==0的状态与mCanRead的状态处理上。

mReadSeeds的处理依赖于mReadLock,mCanRead置为false的处理也依赖于mReadLock的保护,所以IncrementReadSeeds();if(!mCanRead)也受到了mReadLock的保护。

而WatiToWrite(那么将mCanRead置为false的方法)受到了mWriteLock的保护,所以mCanRead对于写的操作是安全的。

因为mReadSeeds==0并不受到写操作的影响,所以不会出现读写互锁的情况。

 

以下是LockClass的全部代码,还望大家多多指教。

另外,感谢msolap的建议。我会尝试用他的建议来优化代码。

 

 

class LockClass
        {
            private bool mCanRead = false;
            private object mWriteLock = new object();
            private object mReadLock = new object();
            private int mReadSeed = 0;


            private void WaitToWrite()
            {
                while (true)
                {
                    lock (mReadLock)
                    {
                        if (mReadSeed == 0)
                        {
                            mCanRead = false;
                            break;
                        }
                    }
                    Thread.Sleep(1);
                }
            }

            public void LockToWrite(Action action)
            {
                lock (mWriteLock)
                {
                    WaitToWrite();

                    try
                    {
                        action();
                    }
                    finally
                    {
                        mCanRead = true;
                    }
                }
            }

            
            private void IncrementReadSeeds()
            {
                lock (mReadLock)
                    mReadSeed++;
            }
            private void DecrementReadSeeds()
            {
                lock (mReadLock)
                    mReadSeed--;
            }

            public T LockToRead<T>(Func<T> action)
            {
                try
                {
                    while (true)
                    {
                        WaitToRead();
                        IncrementReadSeeds();
                        if (!mCanRead)
                        {
                            DecrementReadSeeds();
                            continue;
                        }
                        else
                            break;
                    }

                    return action();
                }
                finally
                {
                    DecrementReadSeeds();
                }
            }

            private void WaitToRead()
            {
                while (!mCanRead)
                {
                    Thread.Sleep(1);
                }
            }

        }
posted on 2010-01-29 23:07  无所畏惧,有所期待  阅读(2664)  评论(35编辑  收藏  举报