线程同步—读写锁

读写锁(reader-writer lock)

  读写锁(reader-writer lock)与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。而读写锁可以有3种状态:读模式加锁状态、写模式加锁状态和不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

  当读写锁是写加锁状态时,在这个锁被释放前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。虽然各操作系统对读写锁的实现各不不同,但当读写锁处于读模式锁住的状态时,而这时有一个线程试图以写模式获取锁,读写锁通常会阻塞随后的读模式锁请求。这样可以读模式锁长期占用,而等待的写模式锁请求一直得不到满足。

读写锁适用情况

  读写锁非常适合对数据读的次数远远大于写的情况。当读写锁在写模式下锁住状态时,它说保护的数据可以被安全地修改,因为一次只有一个线程可以在写模式下拥有读写锁。当读写锁处于读模式下锁住状态时,只要线程先获取了读模式下的读写锁,该锁所保护的数据就可以被多个获得读模式锁的线程读取。

  读写锁也叫共享互斥锁(shared-exclusive lock)。当读写锁是读模式锁住时,就可以说成是以共享模式锁住的。当它是写模式锁住时,就可以说成是以互斥模式锁住的。

读写锁API函数

//头文件
#include <pthread.h>
#include <time.h> //struct timespec结构体

//读写锁API函数
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);  //写模式加锁函数
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);  //读模式加锁函数
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  //解除读写锁函数
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);

读写锁初始化/销毁

  和互斥量一样,读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
【参数】
rwlock:读写锁指针。
attr: 读写锁属性。如果参数值为NULL,表示创建一个默认属性的读写锁。
【说明】pthread_rwlock_init函数动态创建一个读写锁并初始化。

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
【说明】销毁rwlock指针指向的读写锁。
【返回值】两个函数的返回值:成功,返回0;失败,返回错误码。

【扩展】对静态分配的读写锁进行初始化方法
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;

【说明】在使用读写锁占用的内存之前,需要调用pthread_rwlock_destroy函数做清理工作。如果pthread_rwlock_init函数为读写锁分配了资源,pthread_rwlock_destroy函数将释放这些资源。如果在调用pthread_rwlock_destroy之前就释放了读写锁占用的内存空间,那么分配这个锁的资源就会丢失。

读写锁加锁/加锁

  在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock函数。要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock函数。不管以何种方式锁住读写锁,都可以调用pthread_rwlock_unlock函数进行解锁。

#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);  //写模式加锁函数
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);  //读模式加锁函数
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);  //解除读写锁函数

【返回值】所有函数的返回值:成功,返回0;失败,返回错误码。

  Single UNIX Specification还定义了读写锁原语的条件版本。

#include <pthread.h>
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

【返回值】两个函数的返回值:成功,返回0;失败,返回错误码。
【说明】这两个函数是读写锁的条件版本,可以获取锁时,返回值为0,不能获取锁时,线程不会阻塞,而是直接返回EBUSY错误码。

带有超时的读写锁

  和互斥量一样,Single UNIX Specification提供了带有超时的读写锁加锁函数,使应用程序在获取读写锁时避免陷入永久阻塞状态。

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);

【返回值】两个函数的返回值:成功,返回0;失败,返回错误码。
【说明】这两个函数的行为与它们“不计时”的版本类似。abs_timeout参数指向struct timespec结构体,指定线程应该被阻塞的时间。如果线程不能获取读写锁,那么超时到期时,
这两个函数将返回ETIMEDOUT错误。与pthread_mutex_timedlock函数类似,超时指定的是绝对时间,而不是相对时间。

实例1:读写锁的使用方法。在该实例中,多个工作线程获取单个主线程分配给它们的作业,作业请求队列由读写锁保护。

  1 #include <stdlib.h>
  2 #include <pthread.h>
  3 
  4 struct job {
  5     struct job *j_next;
  6     struct job *j_prev;
  7     pthread_t   j_id;   /* tells which thread handles this job */
  8     /* ... more stuff here ... */
  9 };
 10 
 11 struct queue {
 12     struct job      *q_head;
 13     struct job      *q_tail;
 14     pthread_rwlock_t q_lock;
 15 };
 16 
 17 /*
 18  * Initialize a queue.
 19  */
 20 int
 21 queue_init(struct queue *qp)
 22 {
 23     int err;
 24 
 25     qp->q_head = NULL;
 26     qp->q_tail = NULL;
 27     //初始化读写锁
 28     err = pthread_rwlock_init(&qp->q_lock, NULL);
 29     if (err != 0)
 30         return(err);
 31     /* ... continue initialization ... */
 32     return(0);
 33 }
 34 
 35 /*
 36  * Insert a job at the head of the queue.
 37  */
 38 void
 39 job_insert(struct queue *qp, struct job *jp)
 40 {
 41     pthread_rwlock_wrlock(&qp->q_lock); //对读写锁进行写操作加锁
 42     jp->j_next = qp->q_head; //待插入作业的j_next指针指向当前队列中的第1个job结点
 43     jp->j_prev = NULL;
 44     if (qp->q_head != NULL) //如果队列不为空
 45         qp->q_head->j_prev = jp; //当前队列头结点的j_prev指针指向待插入的job结点
 46     else //如果队列为空
 47         qp->q_tail = jp; //队列尾指针q_tail也指向新的job结点
 48     qp->q_head = jp; //队列头指针q_head指向新插入的job结点,即新的job结点插入到了队列头中
 49     pthread_rwlock_unlock(&qp->q_lock); //对读写锁进行解锁
 50 }
 51 
 52 /*
 53  * Append a job on the tail of the queue.
 54  */
 55 void
 56 job_append(struct queue *qp, struct job *jp)
 57 {
 58     pthread_rwlock_wrlock(&qp->q_lock); //对读写锁进行写操作加锁
 59     jp->j_next = NULL;
 60     jp->j_prev = qp->q_tail; //队列尾指针指向当前尾结点的前继结点
 61     if (qp->q_tail != NULL)
 62         qp->q_tail->j_next = jp; //当前队列尾结点的j_next指向新结点jp
 63     else
 64         qp->q_head = jp;    /* list was empty */
 65     qp->q_tail = jp; //队列尾指针指向新结点
 66     pthread_rwlock_unlock(&qp->q_lock);
 67 }
 68 
 69 /*
 70  * Remove the given job from a queue.
 71  */
 72 void
 73 job_remove(struct queue *qp, struct job *jp)
 74 {
 75     pthread_rwlock_wrlock(&qp->q_lock);
 76     if (jp == qp->q_head) { //待删除结点jp是队列头结点
 77         qp->q_head = jp->j_next; //队列头指针指向jp的后继结点
 78         if (qp->q_tail == jp)
 79             qp->q_tail = NULL;
 80         else
 81             jp->j_next->j_prev = jp->j_prev; //jp的后继结点的j_prev指向jp的前继结点
 82     } else if (jp == qp->q_tail) { //待删除结点jp是队列尾结点
 83         qp->q_tail = jp->j_prev; //队列尾指针指向jp的前继结点
 84         jp->j_prev->j_next = jp->j_next; //jp的前继结点的j_next指向jp的后继节点
 85     } else {
 86         jp->j_prev->j_next = jp->j_next; //jp的前继结点的j_next指向jp的后继结点
 87         jp->j_next->j_prev = jp->j_prev; //jp的后继结点的j_prev指向jp的前继结点
 88     }
 89     pthread_rwlock_unlock(&qp->q_lock);
 90 }
 91 
 92 /*
 93  * Find a job for the given thread ID.
 94  */
 95 struct job *
 96 job_find(struct queue *qp, pthread_t id)
 97 {
 98     struct job *jp;
 99 
100     if (pthread_rwlock_rdlock(&qp->q_lock) != 0) //对读写锁进行读操作加锁
101         return(NULL);
102 
103     for (jp = qp->q_head; jp != NULL; jp = jp->j_next)
104         if (pthread_equal(jp->j_id, id))
105             break;
106 
107     pthread_rwlock_unlock(&qp->q_lock);
108     return(jp);
109 }
rwlock.c

【分析】

1.向队列中增加作业或者从队列中删除作业时,都采用了写模式来锁住队列的读写锁。

2.搜索队列时,采用了读模式来锁住队列的读写锁,允许所有的工作线程能并发地搜索队列。

3.工作线程只能从队列中读取与它们的线程ID相匹配的作业。由于作业结构体同一时间只能由一个线程使用,所以不需要额外的加锁。

4.只有在线程搜索队列中的作业的频率远远高于增加或删除作业时,使用读写锁才能改善性能。

猜你喜欢

转载自www.cnblogs.com/yunfan1024/p/11306599.html