{C#} ReaderWriterLockSlim源码一读

ReaderWriterLockSlim支持递归进入ReaderLock或WriterLock,但下面的分析不打算涉及这种情况。

如何读锁不互斥

TryEnterReadLockCore(TimeoutTracker timeout)
{
            .......
            // step1
            EnterMyLock();
            lrwc = GetThreadRWCount(false);
            
            if (lrwc.readercount > 0)
            {
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveReadNotAllowed));
            }

            .......

        // step 2
        for (; ; )
        {
            // We can enter a read lock if there are only read-locks have been given out
            // and a writer is not trying to get in.  
            // 2.1
            if (owners < MAX_READER)
            {
                // Good case, there is no contention, we are basically done
                owners++;       // Indicate we have another reader
                lrwc.readercount++;
                break;
            }

            // 2.2
            if (spincount < MaxSpinCount)
            {
                ExitMyLock();
                if (timeout.IsExpired)
                    return false;
                spincount++;
                SpinWait(spincount);
                EnterMyLock();
                //The per-thread structure may have been recycled as the lock is acquired (due to message pumping), load again.
                if(IsRwHashEntryChanged(lrwc))
                    lrwc = GetThreadRWCount(false);
                continue;
            }

            // 2.3
            // Drat, we need to wait.  Mark that we have waiters and wait.  
            if (readEvent == null)      // Create the needed event 
            {
                LazyCreateEvent(ref readEvent, false);
                if (IsRwHashEntryChanged(lrwc))
                    lrwc = GetThreadRWCount(false);
                continue;   // since we left the lock, start over. 
            }

            retVal = WaitOnEvent(readEvent, ref numReadWaiters, timeout);
            if (!retVal)
            {
                return false;
            }
            if (IsRwHashEntryChanged(lrwc))
                lrwc = GetThreadRWCount(false);
        }

        // step 3
        ExitMyLock();
}
第一步: 进入轻量锁
    private void EnterMyLock()
    {
        if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
            EnterMyLockSpin();
    }

这个锁不是普通的lock。它通过原子操作设置int类型lock,只有一个线程能设置成功,其他线程通过自旋和短暂sleep来等待下次设置生效。
要注意,其他线程不管是EnterReadLock,或者是EnterWriteLock,都会在这里被栏住,直到ExitMyLock被调用。
之后获取读写计数 GetThreadRWCount, 每个线程维护自己的ReaderWriterCount,如果一个线程使用了多个ReaderWriterLockSlim,则会形成一个列表。可以从列表中回收RWC。
为了防止重复进入ReaderLock,如果已有读者,就会跑出异常。

第二步:登记Reader

如果owners没有超出最大值,这是最好的情况,多个reader都可以登记。这里真正体现了读者不互斥。多个线程在调用EnterReadLock之后,都可以顺利地执行后续代码。(释放mylock后,只要owners不超限,其他线程也会进入这段代码,不会被阻塞)

如果owners超出最大值(只靠单纯地增加reader不太可能发生),这里最大的可能是之前其他线程已经EnterWriteLock,后续我们可以从TryEnterWriteLockCore看到,writer会将owners设置成超出值,从而阻挡了读者登记,跳到了2.2.

登记失败,接下来首先会尝试自旋,在自旋时间内,如果writer释放了lock,重新登记读者。

如果尝试过N次自旋后,还是无法成功,只能创建readerEvent(ManualResetEvent),并等待。当writer触发终止态时,所有等待的reader都会得到通知。

第三步:释放轻量锁
    private void ExitMyLock()
    {
        Volatile.Write(ref myLock, 0);
    }

很简单,int lock设成0,其他等待的线程又可以成功设置mylock了。

如何读写互斥

之前讲了写会通过owners排斥读,再看下读怎么排斥写。

    private bool TryEnterWriteLockCore(TimeoutTracker timeout)
    {
        ......

        EnterMyLock();
        lrwc = GetThreadRWCount(true);

        //Can't acquire write lock with reader lock held. 
        if (lrwc != null && lrwc.readercount > 0)
        {
            ExitMyLock();
            throw new LockRecursionException(SR.GetString(SR.LockRecursionException_WriteAfterReadNotAllowed));
        }

        ......

        for (; ; )
        {
            if (IsWriterAcquired())
            {
                // Good case, there is no contention, we are basically done
                SetWriterAcquired();
                break;
            }

            ......

            if (spincount < MaxSpinCount)
            {
                ExitMyLock();
                if (timeout.IsExpired)
                    return false;
                spincount++;
                SpinWait(spincount);
                EnterMyLock();
                continue;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait.
           if (writeEvent == null)     // create the needed event.
           {
               LazyCreateEvent(ref writeEvent, true);
               continue;   // since we left the lock, start over. 
           }

           retVal = WaitOnEvent(writeEvent, ref numWriteWaiters, timeout);
           //The lock is not held in case of failure.
           if (!retVal)
               return false;

        }

        ExitMyLock();

        writeLockOwnerId = id;

        return true;
    }

获取锁不用再讲。看看之后的操作,首先设置标志位。

    private bool IsWriterAcquired()
    {
        return (owners & ~WAITING_WRITERS) == 0;
    }

如果已经登记过reader,此处返回false,writer会进入后续等待,否则设置标志位。

    private void SetWriterAcquired()
    {
        owners |= WRITER_HELD;    // indicate we have a writer.
    }

设置该标志位后,又会将reader阻挡在外。

后续等待的流程差不多,要注意这里创建的writerEvent是AutoResetEvent,意味着一次只能唤醒一个writer。这就避免了writer之间的竞争。

Event何时通知

readerEvent在ExistWriteLock,writerEvent在ExistReadLock。这也很科学。

写一个封装器

不难实现,支持using即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,836评论 6 540
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,275评论 3 428
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,904评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,633评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,368评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,736评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,740评论 3 446
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,919评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,481评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,235评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,427评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,968评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,656评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,055评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,348评论 1 294
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,160评论 3 398
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,380评论 2 379

推荐阅读更多精彩内容