zoukankan      html  css  js  c++  java
  • 线程同步—读写锁

    读写锁(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.只有在线程搜索队列中的作业的频率远远高于增加或删除作业时,使用读写锁才能改善性能。

  • 相关阅读:
    jQuery index()方法使用
    杂记
    Tp框架代码杂记
    tp U方法的{:U('Index/index',array('id'=>$vo[id]))}
    mb_substr=0,5,'utf-8'
    Thinkphp 超长sql语句编写
    http_build_query()生成url字符串
    html_entity_decode 将数据库里的 | 互联网金融 &ldquo;野蛮生长&rdquo; 的休止符| &rdquo转义成”“
    ThinkPHP 左右定界符
    python中unicode和str的组合
  • 原文地址:https://www.cnblogs.com/yunfan1024/p/11306599.html
Copyright © 2011-2022 走看看