• SRWLock 轻量级的读写锁


    map  用于生产者观察模式

    queue 用于生产消费者模式

    SRWLock  轻量级的读写锁,它与临界区对象的不同在于,它分为两个模式来访问共享资源。并假设有两种类型的线程同时工作,一种用在读取共享资源,通常又称为消费线程,另一种用来对共享的资源进程写入操作,通常又称为生产线程。

    共享模式下每个读取线程可以读取共享的数据,不受任何限制,它们可以同时读取共享数据。

    独占模式下只运行一个写入线程访问共享数据,其他读/写线程都会被堵塞。 直到该线程释放了读写锁。

    在多线程的情况下,生产线程和消费线程是无序的,SRWLock是一个指针,它的优势在于可以快速的更新锁的状态,劣势在这个指针可以存放有限的信息,因此SRWLock不能够递归获取,此外一个线程处于共享模式的时候,不能讲自身升级为独占模式。

    初始化读写锁很简单

    VOID WINAPI InitializeSRWLock(  PSRWLOCK SRWLock);
    

    在Winbase.h文件中

    typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK;
    1.  
      typedef struct _RTL_SRWLOCK {
    2.  
      PVOID Ptr;
    3.  
      } RTL_SRWLOCK, *PRTL_SRWLOCK;



    可见读写锁确实只是一个指针变量。

    使用读写锁:

    在消费线程中

    AcquireSRWLockShared

    *******读取操作

    ReleaseSRWLockShared

    在生产线程中

    AcquireSRWLockExclusive

    *******写操作

    ReleaseSRWLockExclusive

    这个读写锁和关键段一样方便使用,作者经过测试发现读写锁的性能较关键有很大的提升,这是因为读写锁是基于原子访问的,关键段是基于事件内核对象的,从用户模式到内核模式的切换占用了大量的时钟周期。

    剖析SRWLock,以React OS下对读写锁的实现代码来看其工作原理。

    在实现中SRWLock 的这个指针变量是这样来标识信息的

    该指针的低4位被用于4个不同的标志,它们有其对应的宏定义

    1.  
      #define RTL_SRWLOCK_OWNED_BIT 0
    2.  
      #define RTL_SRWLOCK_CONTENDED_BIT 1
    3.  
      #define RTL_SRWLOCK_SHARED_BIT 2
    4.  
      #define RTL_SRWLOCK_CONTENTION_LOCK_BIT 3
    5.  
      #define RTL_SRWLOCK_OWNED (1 << RTL_SRWLOCK_OWNED_BIT) //0位为1 表示有线程正在读/写共享资源
    6.  
      #define RTL_SRWLOCK_CONTENDED (1 << RTL_SRWLOCK_CONTENDED_BIT) //1位为1 表示一个或者多个生产线程在等待独占资源
    1.  
      #define RTL_SRWLOCK_SHARED (1 << RTL_SRWLOCK_SHARED_BIT) //2位为1 表示一个或者多个消费线程在等待读取资源
    2.  
      #define RTL_SRWLOCK_CONTENTION_LOCK (1 << RTL_SRWLOCK_CONTENTION_LOCK_BIT) //3位为1 标识有一个线程在获取WAITBLOCK结构指针
    3.  
      #define RTL_SRWLOCK_MASK (RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED |
    4.  
      RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENTION_LOCK)
    5.  
      #define RTL_SRWLOCK_BITS 4


    SRWLock 的高28位是一个指针,为什么只有28位呢,是因为它要指向对象的对齐方式是16,即地址的最低4位都是0,比如都是这样的0x00124710,0x00124720,0x00124850,   当需要获取指针所指向的对象时,只要将低4位全部置0,就可以得到对象的地址。

    SRWLock 在同时有一个以上线程读写资源的时候,它指向一个结构体链表。

    在有其他线程在读/资源的时候,一个线程调用AcquireSRWLockExclusive或者AcquireSRWLockShared  时会将一个在栈上构建的结构体挂入SRWLock 所指向的链表,这样每个将要读/写资源的线程都会在栈上构建这么一个结构体,并将结构体挂入链表中。

    该结构体是这样定义的

    1.  
      typedef struct _RTLP_SRWLOCK_WAITBLOCK
    2.  
      {
    3.  
       
    4.  
      LONG SharedCount; //有多少线程 在等待读取
    5.  
      volatile struct _RTLP_SRWLOCK_WAITBLOCK *Last;
    6.  
      volatile struct _RTLP_SRWLOCK_WAITBLOCK *Next; //链表节指针
    7.  
       
    8.  
      union
    9.  
      {
    10.  
      LONG Wake; //非0表示可以被唤醒,0表示继续睡眠
    11.  
      struct
    12.  
      {
    13.  
      PRTLP_SRWLOCK_SHARED_WAKE SharedWakeChain; //需要被唤醒的消费线程链表
    14.  
      PRTLP_SRWLOCK_SHARED_WAKE LastSharedWake; //上一个被唤醒的消费线程
    15.  
      };
    16.  
      };
    17.  
      BOOLEAN Exclusive; //1表示该结构体对象由生产线程构建在栈上,0表示结构体由消费线程构建在栈上
    18.  
      } volatile RTLP_SRWLOCK_WAITBLOCK, *PRTLP_SRWLOCK_WAITBLOCK;

    下面是单向链表结构 ,代表每个需要被唤醒的消费线程

    1.  
      typedef struct _RTLP_SRWLOCK_SHARED_WAKE
    2.  
      {
    3.  
      LONG Wake; //唤醒标志,非0唤醒,0睡眠
    4.  
      volatile struct _RTLP_SRWLOCK_SHARED_WAKE *Next;
    5.  
      } volatile RTLP_SRWLOCK_SHARED_WAKE, *PRTLP_SRWLOCK_SHARED_WAKE;

    初始化读写锁,只是简单的将指针置为0

    1.  
      NTAPI
    2.  
      RtlInitializeSRWLock(OUT PRTL_SRWLOCK SRWLock)
    3.  
      {
    4.  
      SRWLock->Ptr = NULL;
    5.  
      }



    AcquireSRWLockExclusive

    它对应的函数是RtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock), 由于这个函数的代码比较长,这里先贴出大体结构,然后分部分注释。

    1.  
      NTAPI
    2.  
      RtlAcquireSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock)
    3.  
      {
    4.  
      __ALIGNED(16) RTLP_SRWLOCK_WAITBLOCK StackWaitBlock; //构建于栈上的 对齐为16字节的 等待块
    5.  
      PRTLP_SRWLOCK_WAITBLOCK First, Last;
    6.  
       
    7.  
      if (InterlockedBitTestAndSetPointer(&SRWLock->Ptr,
    8.  
      RTL_SRWLOCK_OWNED_BIT)) //如果有其他线程在访问资源,进入循环,该原子访问函数返回之前的位值
    9.  
      {
    10.  
      LONG_PTR CurrentValue, NewValue;
    11.  
       
    12.  
      while (1)
    13.  
      {
    14.  
      CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr;
    15.  
       
    16.  
      if (CurrentValue & RTL_SRWLOCK_SHARED)
    17.  
      {
    18.  
      if (CurrentValue & RTL_SRWLOCK_CONTENDED)
    19.  
      {
    20.  
      goto AddWaitBlock;
    21.  
      }
    22.  
      else
    23.  
      {
    24.  
      } //Part1, 如果有线程在读取资源,且没有其他生产线程在独占资源
    25.  
      }
    26.  
      else
    27.  
      {
    28.  
      if (CurrentValue & RTL_SRWLOCK_OWNED)
    29.  
      {
    30.  
       
    31.  
      if (CurrentValue & RTL_SRWLOCK_CONTENDED)
    32.  
      {
    33.  
      AddWaitBlock:
    34.  
      //Part2,如果有其他线程在等待独占资源
    35.  
      }
    36.  
      else
    37.  
      {
    38.  
      //Part3,如果有线程独占资源,且没有其他线程在等待资源
    39.  
      }
    40.  
      }
    41.  
      else
    42.  
      {
    43.  
      if (!InterlockedBitTestAndSetPointer(&SRWLock->Ptr,
    44.  
      RTL_SRWLOCK_OWNED_BIT))
    45.  
      {
    46.  
      break;
    47.  
      }
    48.  
      }
    49.  
      }
    50.  
       
    51.  
      YieldProcessor(); 执行空指令
    52.  
      }
    53.  
      }
    54.  
      }
    55.  
       


    最后一个函数  只是执行一个NOP指令,只是用来做延时而已,

        #define YieldProcessor() __asm__ __volatile__("nop");

    Part1

    1.  
      if (CurrentValue & RTL_SRWLOCK_CONTENDED)
    2.  
      {
    3.  
      goto AddWaitBlock;
    4.  
      }
    5.  
      else
    6.  
      {
    7.  
      StackWaitBlock.Exclusive = TRUE; //标识结构体位于生产线程的栈上
    8.  
      StackWaitBlock.SharedCount = (LONG)(CurrentValue >> RTL_SRWLOCK_BITS); //目前的高28位标识多少个线程在等待读取
    9.  
      StackWaitBlock.Next = NULL;
    10.  
      StackWaitBlock.Last = &StackWaitBlock;
    11.  
      StackWaitBlock.Wake = 0; //初始化这个栈上的结构体
    12.  
      NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_SHARED | RTL_SRWLOCK_CONTENDED | RTL_SRWLOCK_OWNED;//设置新指针为该栈上结构地址
    13.  
      并设置标志位
    14.  
      if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr,
    15.  
      (PVOID)NewValue,
    16.  
      (PVOID)CurrentValue) == CurrentValue) 更换指针为新的值
    17.  
      {
    18.  
      RtlpAcquireSRWLockExclusiveWait(SRWLock,
    19.  
      &StackWaitBlock); 进入生产线程的等待函数
    20.  
      break;
    21.  
      }



    看下这个生产线程的等待函数

    1.  
      NTAPI
    2.  
      RtlpAcquireSRWLockExclusiveWait(IN OUT PRTL_SRWLOCK SRWLock,
    3.  
      IN PRTLP_SRWLOCK_WAITBLOCK WaitBlock)
    4.  
      {
    5.  
      LONG_PTR CurrentValue;
    6.  
       
    7.  
      while (1)
    8.  
      {
    9.  
      CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr;
    10.  
      if (!(CurrentValue & RTL_SRWLOCK_SHARED))
    11.  
      {
    12.  
      if (CurrentValue & RTL_SRWLOCK_CONTENDED)
    13.  
      {
    14.  
      if (WaitBlock->Wake != 0)
    15.  
      {
    16.  
      break;
    17.  
      }
    18.  
      }
    19.  
      else
    20.  
      {
    21.  
      break;
    22.  
      }
    23.  
      }
    24.  
       
    25.  
      YieldProcessor(); //只有在没有线程在读取,没有其他生产线程在独占,或者独占的线程将该线程的WAKE标志设为非0,时退出死循环
    26.  
      }
    27.  
      }

    该函数保证了没个生产线程以独占的模式运行,其他生产线程只能在这里死循环,只要被唤醒,或者独占标志位被清除。

    Part2:

    1.  
      AddWaitBlock:
    2.  
      StackWaitBlock.Exclusive = TRUE;
    3.  
      StackWaitBlock.SharedCount = 0;
    4.  
      StackWaitBlock.Next = NULL;
    5.  
      StackWaitBlock.Last = &StackWaitBlock;
    6.  
      StackWaitBlock.Wake = 0; 初始化结构体
    7.  
       
    8.  
      First = RtlpAcquireWaitBlockLock(SRWLock); 根据28位的指针去掉标志位,获取指向的链表表头,
    9.  
      if (First != NULL)
    10.  
      {
    11.  
      Last = First->Last;
    12.  
      Last->Next = &StackWaitBlock;
    13.  
      First->Last = &StackWaitBlock; //将该线程栈上的结构体挂入链表的最后面,将表头的Last指向最后这个挂入的
    14.  
       
    15.  
      RtlpReleaseWaitBlockLock(SRWLock);
    16.  
       
    17.  
      RtlpAcquireSRWLockExclusiveWait(SRWLock,
    18.  
      &StackWaitBlock); //进入等待循环
    19.  
       
    20.  
      break;
    21.  
      }


    RtlpAcquireWaitBlockLock  揭示了 如何通过28位的指针工作

    1.  
      NTAPI
    2.  
      RtlpAcquireWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock)
    3.  
      {
    4.  
      LONG_PTR PrevValue;
    5.  
      PRTLP_SRWLOCK_WAITBLOCK WaitBlock;
    6.  
       
    7.  
      while (1)
    8.  
      {
    9.  
      PrevValue = InterlockedOrPointer(&SRWLock->Ptr,
    10.  
      RTL_SRWLOCK_CONTENTION_LOCK); //低位 第3位如果是1表示,有其他线程在调用该函数根据指针获取链表头
    11.  
      这里保证了 只有一个线程可以访问链表,其他需要访问的要等待
    12.  
      if (!(PrevValue & RTL_SRWLOCK_CONTENTION_LOCK))
    13.  
      break;
    14.  
       
    15.  
      YieldProcessor();
    16.  
      }
    17.  
       
    18.  
      if (!(PrevValue & RTL_SRWLOCK_CONTENDED) ||
    19.  
      (PrevValue & ~RTL_SRWLOCK_MASK) == 0)
    20.  
      {
    21.  
      RtlpReleaseWaitBlockLock(SRWLock); //如果现在没有处于独占模式 或者 高28位 为0 ,错误 释放RTL_SRWLOCK_CONTENTION_LOCK标志位
    22.  
      return NULL;
    23.  
      }
    24.  
       
    25.  
      WaitBlock = (PRTLP_SRWLOCK_WAITBLOCK)(PrevValue & ~RTL_SRWLOCK_MASK); 将指针与0x11111110 与操作, 将低4位标志清除,构成实际地址
    26.  
       
    27.  
      return WaitBlock;
    28.  
      }
    1.  
      NTAPI
    2.  
      RtlpReleaseWaitBlockLock(IN OUT PRTL_SRWLOCK SRWLock)
    3.  
      {
    4.  
      InterlockedAndPointer(&SRWLock->Ptr,
    5.  
      ~RTL_SRWLOCK_CONTENTION_LOCK); 只是简单将该标志位设为0
    6.  
      }



    Part3:

    1.  
      StackWaitBlock.Exclusive = TRUE;
    2.  
      StackWaitBlock.SharedCount = 0;
    3.  
      StackWaitBlock.Next = NULL;
    4.  
      StackWaitBlock.Last = &StackWaitBlock;
    5.  
      StackWaitBlock.Wake = 0; 初始化结构体
    6.  
       
    7.  
      NewValue = (ULONG_PTR)&StackWaitBlock | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED; 将结构体地址构成新的指针
    8.  
      if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr,
    9.  
      (PVOID)NewValue,
    10.  
      (PVOID)CurrentValue) == CurrentValue) 替换原来的指针
    11.  
      {
    12.  
      RtlpAcquireSRWLockExclusiveWait(SRWLock,
    13.  
      &StackWaitBlock); 进入等待
    14.  
      break;
    15.  
      }



    第一部分 和第三不分 代码差不多的,唯一的区别就是 第一不分多设置了一个标志位 RTL_SRWLOCK_SHARED,它们都是初始化栈上的结构体 并将结构体的地址加上标志位,然后替换为指针的值。

    AcquireSRWLockExclusive  总结:

    1、当有线程还在读取时,但是没有被其它生产线程独占, 那么挂入栈等待块,设置标志 (读取  独占 拥有),进入等待,读取线程全部Release时 线程等待结束,以独占模式访问资源

    2、有其他生产线程在等待独占时,将栈块挂入SRWLock指针所指向链表的末尾,进入等待,在前面所有已挂入的等待都Release时,线程才结束等待 以独占模式访问资源。

    3、如果有线程在独占,但是没有其他在等待独占的,那么挂入栈等待块,设置标志 (独占 拥有),进入等待,读取线程全部Release时 线程等待结束,以独占模式访问资源

    ReleaseSRWLockExclusive

    1.  
      NTAPI
    2.  
      RtlReleaseSRWLockExclusive(IN OUT PRTL_SRWLOCK SRWLock)
    3.  
      {
    4.  
      LONG_PTR CurrentValue, NewValue;
    5.  
      PRTLP_SRWLOCK_WAITBLOCK WaitBlock;
    6.  
       
    7.  
      while (1)
    8.  
      {
    9.  
      CurrentValue = *(volatile LONG_PTR *)&SRWLock->Ptr;
    10.  
       
    11.  
      if (!(CurrentValue & RTL_SRWLOCK_OWNED)) //此时如果不处于拥有状态,抛出异常
    12.  
      {
    13.  
      RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED);
    14.  
      }
    15.  
       
    16.  
      if (!(CurrentValue & RTL_SRWLOCK_SHARED)) //必须不处于读取状态
    17.  
      {
    18.  
      if (CurrentValue & RTL_SRWLOCK_CONTENDED)
    19.  
      {
    20.  
      WaitBlock = RtlpAcquireWaitBlockLock(SRWLock);
    21.  
      if (WaitBlock != NULL)
    22.  
      {
    23.  
      RtlpReleaseWaitBlockLockExclusive(SRWLock,
    24.  
      WaitBlock); 如果有等待独占的线程,调用该函数,将指针指向的链表头传给它
    25.  
       
    26.  
      break;
    27.  
      }
    28.  
      }
    29.  
      else
    30.  
      {
    31.  
       
    32.  
      ASSERT(!(CurrentValue & ~RTL_SRWLOCK_OWNED));
    33.  
       
    34.  
      NewValue = 0;
    35.  
      if ((LONG_PTR)InterlockedCompareExchangePointer(&SRWLock->Ptr,
    36.  
      (PVOID)NewValue,
    37.  
      (PVOID)CurrentValue) == CurrentValue) //如果没有等待独占的,指针置为0
    38.  
      {
    39.  
      break;
    40.  
      }
    41.  
      }
    42.  
      }
    43.  
      else
    44.  
      {
    45.  
      RtlRaiseStatus(STATUS_RESOURCE_NOT_OWNED); 如果处于读取状态 抛出异常
    46.  
      }
    47.  
       
    48.  
      YieldProcessor();
    49.  
      }
    50.  
      }

    RtlpReleaseWaitBlockLockExclusive:

      1.  
        NTAPI
      2.  
        RtlpReleaseWaitBlockLockExclusive(IN OUT PRTL_SRWLOCK SRWLock,
      3.  
        IN PRTLP_SRWLOCK_WAITBLOCK FirstWaitBlock)
      4.  
        {
      5.  
        PRTLP_SRWLOCK_WAITBLOCK Next;
      6.  
        LONG_PTR NewValue;
      7.  
         
      8.  
         
      9.  
        Next = FirstWaitBlock->Next;
      10.  
        if (Next != NULL) 如果还有其他在等待的线程
      11.  
        {
      12.  
        NewValue = (LONG_PTR)Next | RTL_SRWLOCK_OWNED | RTL_SRWLOCK_CONTENDED;
      13.  
        if (!FirstWaitBlock->Exclusive) 如果第一个等待的线程是读取线程
      14.  
        {
      15.  
        ASSERT(Next->Exclusive);
      16.  
        Next->SharedCount = FirstWaitBlock->SharedCount; //复制读取线程计数
      17.  
         
      18.  
        NewValue |= RTL_SRWLOCK_SHARED;
      19.  
        }
      20.  
         
      21.  
        Next->Last = FirstWaitBlock->Last; 将Last指向链尾的对象
      22.  
        }
      23.  
        else //如果只有一个在等待的线程
      24.  
        {
      25.  
        if (FirstWaitBlock->Exclusive) //如果该线程是生产线程,简单设置一个拥有位
      26.  
        NewValue = RTL_SRWLOCK_OWNED;
      27.  
        else
      28.  
        { //如果该线程是消费线程,设置高位为线程计数,低位为标志 拥有 和 读取
      29.  
        ASSERT(FirstWaitBlock->SharedCount > 0);
      30.  
         
      31.  
        NewValue = ((LONG_PTR)FirstWaitBlock->SharedCount << RTL_SRWLOCK_BITS) |
      32.  
        RTL_SRWLOCK_SHARED | RTL_SRWLOCK_OWNED;
      33.  
        }
      34.  
        }
      35.  
         
      36.  
        (void)InterlockedExchangePointer(&SRWLock->Ptr, (PVOID)NewValue); 设置新的指针
      37.  
         
      38.  
        if (FirstWaitBlock->Exclusive)
      39.  
        {
      40.  
        (void)InterlockedOr(&FirstWaitBlock->Wake, 将第一个等待的线程唤醒,如果它是生产线程的话。
      41.  
        TRUE);
      42.  
        }
      43.  
        else //如果第一个等待唤醒的是一个消费线程的等待块,根据SharedWakeChain链表, 唤醒每一个等待的消费线程。
      44.  
        {
      45.  
        PRTLP_SRWLOCK_SHARED_WAKE WakeChain, NextWake;
      46.  
        WakeChain = FirstWaitBlock->SharedWakeChain;
      47.  
        do
      48.  
        {
      49.  
        NextWake = WakeChain->Next;
      50.  
         
      51.  
        (void)InterlockedOr((PLONG)&WakeChain->Wake,
      52.  
        TRUE);
      53.  
         
      54.  
        WakeChain = NextWake;
      55.  
        } while (WakeChain != NULL);
      56.  
        }
      57.  
        }
      58.  
    好不容易活着
  • 相关阅读:
    从 OKR 工作法到 OKRs-E,落地OKR不能错过的转变
    OKR : 不要让目标仅仅成为口号
    [MySQL]Software caused connection abort: recv failed 问题分析与解决
    VS2010 vcpkgsrv.exe进程CPU占用高的解决方法
    Lync Server 2013企业版部署系列之九:前端部署后续工作
    Lync Server 2013企业版部署系列之八:安装lync server系统
    Lync Server 2013企业版部署系列之七:使用拓扑生成器规划拓扑
    Lync Server 2013企业版部署系列之六:AD准备
    Lync Server 2013企业版部署系列之五:前端服务器软件准备
    Lync Server 2013企业版部署系列之四:SQL准备
  • 原文地址:https://www.cnblogs.com/iwana/p/13644181.html
Copyright © 2020-2023  润新知