要实现读写锁,首先要知道读写锁的特性,除了“读者可并发,写者要排它”之外还要考虑避免写者饥饿的问题。综合考虑后可以讲读写锁的实现总结为一下四点:

  1. 当已经被施加写锁的时候,读锁写锁都不能在施加(写锁只能锁一次)

  2. 当已经被施加读锁时,还可以继续施加读锁,但不能施加写锁

  3. 有等待的写者时不能在获取读锁(保证写者优先)

  4. 解锁时有如果写者在等待,不能唤醒读者。

我们使用互斥量(mutex)和条件变量来实现读写锁,这也是大多数系统的实现方式。另外本文主要想说明的是读写锁的实现思路,所以这里只实现最基本的三个操作:申请读锁、申请写锁、解锁。而不实现初始化、trylock、销毁锁等操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
typedef struct
{
   pthread_mutex_t rw_mutex; //对整个结构体提供互斥访问
   pthread_cond_t rw_condreaders;//用于通知申请读锁的线程
   pthread_cond_t rw_condwriters;//用于通知申请写锁的线程
   int rw_waitreaders; //等待申请读锁的线程数
   int rw_waitwriters; //等待申请写锁的线程数
   int rw_refcount; //表示读写锁的状态,如果是-1表示它是一个写锁
 }pthread_rwlock_t; //0表示它是可用的,大于0表示当前容纳的读锁数量


int pthread_rwlock_rdlock(pthread_rwlock_t *rw) //申请读锁
{
  int result; //返回值(出错状态)

  pthread_mutex_lock(&rw->rw_mutex);
  //当写锁正在使用时不能上读锁,当锁可用但有线程等待申请写锁时一样也不能上读锁,这一点体现出来“写者优先”
  while(rw->rw_refcount<0||rw->rw_waitwriters>0)
  {
    rw->rw_waitreaders++;
    result=pthread_cond_wait(&rw->rw_condreaders,&rw->rw_mutex);//等待读条件就绪
    rw->rw_waitreaders--;
    if(result!=0)
       break;
  }
  if(result==0)
       rw->rw_refcount++; //又有一个新的线程获取了读锁

  pthread_mutex_unlock(&rw->rw_mutex);
  return (result);
}


int pthread_rwlock_wrlock(pthread_rwlock_t *rw) //申请写锁
{
  int result; //返回值(出错状态)

  pthread_mutex_lock(&rw->rw_mutex);
    //这里只检查锁是否可用
  while(rw->rw_refcount!=0)
 {
   rw->rw_waitwriters++;
   result=pthread_cond_wait(&rw->rw_condwriters,&rw->rw_mutex);//等待写条件就绪
   rw->rw_waitwriters--;
   if(result!=0)
     break;
 }
  if(result==0)
    rw->rw_refcount=-1; //线程获取了写锁

  pthread_mutex_unlock(&rw->rw_mutex);
  return (result);
}




int pthread_rwlock_unlock(pthread_rwlock_t *rw)//释放锁(读锁、写锁)
{
   int result;


  pthread_mutex_lock(&rw->rw_mutex);
  if(rw->refcount>0)
     rw->refcount--;
  else if(rw->refcount==-1)
     rw->refcount=0;
  else
     printf("rw->refcount=%d\n",rw->refcount);
    //先看是否有写者在等待,如果有的话先唤醒写者,这是“写者优先”的另一体现
 if(rw->rw_waitwriters>0)
 {//不能写成if(rw->rw_waitwriters>0&&rw->refcount==0)
   if(rw->refcount==0)
      result=pthread_cond_signal(&rw->rw_condwriters);
 }
 else if(rw->rw_waitreaders>0)
   result=pthread_cond_signal(&rw->rw_condreaders);


  pthread_mutex_unlock(rw->rw_mutex);
  return result;
}

不能写成if(rw->rw_waitwriters>0&&rw->refcount==0)的原因是: 这样会造成写者"饥饿",也就是应当保证,当有写者在等待申请锁的时候,不能在让读者来申请锁,否则由于读者可以多次加锁,一个持续的读请求流可能永远阻塞某个等待的写者。写成if(rw->rw_waitwriters>0&&rw->refcount==0),当第一个条件满足而第二个条件不满足,也就是有写者在等待,而还有读者在使用锁的情况,我们应该仅仅释放一个读锁,别的什么也不做,而这里却会执行else if,导致唤醒等待的读者,使读者获取到锁。