读写锁(RWLock)

  读写锁也可以对一个资源进行保护。但是它跟关键段有所不同,读写锁允许我们区分那些想要读取资源的线程和更改资源的线程。让所有的读取资源的线程同时工作是可行的,因为读取不会破坏数据。只有当写入时才需要对写入线程进行同步。写入线程必须独占资源,它工作时无论是读取还是写入线程都必须等待。

  使用读写锁之前,需要分配一个SRWLOCK结构,并调用InitializeSRWLock初始化它。

 Void InitializeSRWLock(PSRWLOCK SRWLock);

  初始化完成之后,写入者线程可以调用AcquireSRWLockExclusive,将SRWLock对象地址传入,获得对被保护资源的独占访问。

Void AcquireSRWLockExclusive(PSRWLOCK SRWLock);

  独占访问结束后,需要调用:

Void ReleaseSRWLockExclusive(PSRWLOCK SRWLock);

  解除对资源的独占。

  对于读取者线程,可以通过调用:

Void AcquireSRWLockShared(PSRWLOCK SRWLock);

  获得对资源的共享访问。

Void ReleaseSRWLockShared(PSRWLOCK SRWLock);

  释放对资源的共享。

  读写锁不能为了多次写入资源而多次锁定资源,然后再多次调用ReleaseSRWLock*来释放对资源的锁定 。

  多线程会导致一些同步开销。如果使用单线程访问一个资源需要x ms,那么使用两个线程花费的时间将会>2x。因为除了同步开销外,还有多个cpu之间必须相互通信以维护高速缓存的一致性。Cpu个数越多,这种花费越多。

  读写锁执行读取操作的性能要优于写入操作。这是因为两个线程可以同时读取,但是需要写入的时候只能一个一个写入。

  对比各种线程同步方法,读写锁和关键段性能差不多,有些情况下读写锁甚至更优,而且还允许多个线程同时读取。因此推荐使用读写锁。使用互斥量性能是差的。这是因为等待互斥量以及后来的释放互斥量需要线程调用API,而这又会导致在用户模式和内核模式之间切换。在下一章介绍的使用内核对象进行同步时还会详细介绍。

  使用读写锁也可以解决生产者-消费者问题。

  如果读取者线程没有数据可读,它应该将锁释放并等待,直到生产者写入新数据为止。如果写入者将缓冲区写满,它同样应该释放锁并进入睡眠状态。要实现这样的线程同步是很复杂的,windows为我们提供一些函数简化了这些操作。

  Windows提供SleepConditionVariableCS或SleepConditionVariableSRW函数,等待条件变量。线程在等待该条件变量时,会以原子方式把锁释放并将自己阻塞,直到该条件变量被触发时为止。

<SPAN style="FONT-SIZE: 18px">Bool SleepConditionVariableCS(
  PCONDITION_VARIABLE pConditionVariable,
  PCRITICAL_SECTION pCriticalSection,
  DWORD dwMilliseconds);
Bool SleepConditionVariableSRW(
  PCONDITION_VARIABLE pConditionVariable,
  PSRWLOCK pSRWLock,
  DWORD dwMilliseconds
  ULONG Flags);</SPAN>

  pConditonVariable指向一个以初始化的条件变量,调用线程将等待该条件变量。第二个参数指向一个关键段或是SRWLock对象。该关键段或SRWLock用来同步对共享资源的访问。Flags指定一旦条件变量被触发,线程将以何种方式获得锁。对读取者线程来说应该传入CONDITION_VARIABLE_LOCKMODE_SHARED表示希望共享对资源的访问。对于写入者线程应该传入0,表示独占资源。

  dwMilliseconds表示我们希望线程花多少时间来等待条件被触发。在指定的时间用完时,如果条件变量尚未被触发,函数返回false,否则为true。

  当另一个线程检测到相应的条件已经满足时,比如存在一个元素可以让读取者线程读取。它会调用WakeConditionVariable或WakeAllConditionVariable,触发条件变量。这样调用Sleep*函数而阻塞在该条件变量的线程会被唤醒。

<SPAN style="FONT-SIZE: 18px">Void WakeConditonVariable(
   PCONDITION_VARIABLE ConditionVariable);
Void WakeAllConditionVariable(
   PCONDITION_VARIABLE ConditionVariable);</SPAN>

  WakeConditionVariable会使SleepConditionVariable*等待的同一个条件变量被触发的线程得到锁并返回。当此线程释放这个锁的时候,不会唤醒其他正在等待此条件变量的线程。