首页 > 操作系统 > 用于线程同步的读写锁(Readers/writer locks)

用于线程同步的读写锁(Readers/writer locks)

2010年2月21日 发表评论 阅读评论

  读写锁(readers/writer lock)的具体含义是文如其名:对一个资源有多个读者而无写入者,或者是只有一个写入者而没有其他的写入者或读出者。

  这种情况是经常会用到的,这就需要有一种专用于这个目的的特殊的同步元素。

  很多情况下,你需要有个数据结构在一堆线程之间共享。当然,你希望在一个时刻只能有一个线程可以对这个数据结构执行写入操作。如果同时有多个线程能对其写入,那么就有可能一个线程写入的数据会覆盖其他线程所写入的数据。为了防止这种情况发生,写线程可以用独占的方式获取“读写锁”,也就是说只有它才能够访问这个数据结构。不过需要注意这个访问的独占性严格受控于随意的方式。也就是说这是由系统设计者来处理的,是由系统设计者来确保所有访问那个数据区域的线程通过读写锁进行同步。

  对于读操作则是相反地。由于读操作是非破坏性的,所以可有多个线程读取数据。这里很明确的一点就是当任何线程或任何多个线程在读取数据区域的时候不能够有线程对这个数据区域执行写操作。不然的话,当一个读线程在读取部分数据区域的时候被一个写线程抢占,之后再恢复运行,继续读取数据,读到的是更新的被更新后的数据,这样会产生混乱的,结果就是数据的不一致性。

  下面看看使用读写锁用到的函数:

  前两个函数是用来为读写锁初始化库的内部存储区域:

int
pthread_rwlock_init (pthread_rwlock_t *lock,
                     const pthread_rwlockattr_t *attr);

int
pthread_rwlock_destroy (pthread_rwlock_t *lock);

  pthread_rwlock_init()函数使用了lock参数,比按照attr所设定的属性初始化这个参数。在我们的例子中,我们会使用NULL作为属性,这样就按照默认属性初始化。

  当使用完读写锁之后,就需要使用pthread_rwlock_destory()函数对其销毁。你不能够使用一个未初始化或已被销毁的读写锁。

  之后,我们需要获取合适类型的锁。如上所述,有两种基本类型的锁:读操作需要非独占的访问、写操作需要独占的访问。为了函数名易懂,函数名都是按照锁的类型进行命名的:

int
pthread_rwlock_rdlock (pthread_rwlock_t *lock);

int
pthread_rwlock_tryrdlock (pthread_rwlock_t *lock);

int
pthread_rwlock_wrlock (pthread_rwlock_t *lock);

int
pthread_rwlock_trywrlock (pthread_rwlock_t *lock);

  这里一共有四个函数,而不是预想的两个。两个预想的函数是pthread_rwlock_rdlock()与pthread_rwlock_wrlock(),这两个函数分别用于读与写。这些是阻塞函数,当锁对于所选操作不可用的时候,线程就会阻塞。当锁在适当情况下可用,线程就是解除阻塞。由于线程通过该函数解除阻塞,就可以假设访问锁所保护的资源是安全的。

  有些时候,线程可能不想被阻塞,而是查看一下它是否能够获得那个锁。这就是那个“尝试(try)”版本函数的由来。需要注意的是尝试版的函数在能够获取锁的时候会获取这个锁,而在不能获取的时候它们也不会阻塞线程,它只会返回一个错误标识。它们在能够获取锁的时候会获取锁的原因是简单的。假设有线程要获取这个锁来执行读取操作,不过不想在锁不可用的时候阻塞。这个线程就会调用pthread_rwlock_tryrdlock()函数,之后它被告知可以获得这个锁。如果这时pthread_rwlock_tryrdlock()函数不分配这个锁,就可能有坏事发生——另外一个能够抢占这个线程的线程执行了,并且这第二个线程可能会用不兼容的模式锁定了资源。由于第一个线程实际上没有获取这个锁,当其确实开始获取锁的时候,它可能就会使用pthread_rwlock_rdlock()函数,这样的话它就会被阻塞,因为该资源在这种模式下已经不可用了。所以,如果在可以获取锁的时候我们没有获取,即使使用了try版的函数,最终也有可能被阻塞。

  最后,不论锁是被如何使用的,我们都要释放它:

int
pthread_rwlock_unlock (pthread_rwlock_t *lock);

  一旦线程完成了对资源的操作之后,它必须通过调用这个函数释放锁。如果锁按照其他等待线程所需要的模式已经可用了,那么那个等待线程就会进入就绪状态。

  需要注意我们不能够使用互斥体来完成这种类型的同步。互斥体是一个单线程的代理,这对于写操作是可以的,一旦涉及到读的情况就会完全失败,因为使用互斥体的话只能够允许一个线程执行读操作。而信号量也是不能使用的,因为使用它的话就不能分辨两者模式的访问了,信号量可以允许多个读线程的访问,不过当一个写线程试图获取它的时候,就和读线程获取它一样不能分辨出来,这样的话结果就是对一个资源可能会有多个读操作与一个或多个写操作同时存在,这是非常糟糕的!

Related posts:

  1. 用于线程同步的Sleepon锁
  2. 用于线程同步的条件变量(Condition Variables)
  3. 内核状态
  4. 进程间通信的发/收/回复的健壮实现
  5. 互不关联的多线程

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.