• 【Linux】读写锁


    读写锁

    读写锁从字面意思我们也可以知道,它由「读锁」和「写锁」两部分构成,如果只读取共享资源用「读锁」加锁,如果要修改共享资源则用「写锁」加锁。

    所以,读写锁适用于能明确区分读操作和写操作的场景。

    读写锁的工作原理是:

    • 当「写锁」没有被线程持有时,多个线程能够并发地持有读锁,这大大提高了共享资源的访问效率,因为「读锁」是用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据。
    • 但是,一旦「写锁」被线程持有后,读线程的获取读锁的操作会被阻塞,而且其他写线程的获取写锁的操作也会被阻塞。

    所以说,写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。

    知道了读写锁的工作原理后,我们可以发现,读写锁在读多写少的场景,能发挥出优势。

    另外,根据实现的不同,读写锁可以分为「读优先锁」和「写优先锁」。

    读优先锁期望的是,读锁能被更多的线程持有,以便提高读线程的并发性,它的工作方式是:当读线程 A 先持有了读锁,写线程 B 在获取写锁的时候,会被阻塞,并且在阻塞过程中,后续来的读线程 C 仍然可以成功获取读锁,最后直到读线程 A 和 C 释放读锁后,写线程 B 才可以成功获取读锁。如下图:

     而写优先锁是优先服务写线程,其工作方式是:当读线程 A 先持有了读锁,写线程 B 在获取写锁的时候,会被阻塞,并且在阻塞过程中,后续来的读线程 C 获取读锁时会失败,于是读线程 C 将被阻塞在获取读锁的操作,这样只要读线程 A 释放读锁后,写线程 B 就可以成功获取读锁。如下图:

     

    归纳总结:

     1. 函数原型:

    #include <pthread.h>
    pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);  // 初始化读写锁
    pthread_rwlock_destroy(pthread_rwlock_t *rwlock);    // 销毁读写锁

    #include <pthread.h>
    pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);  // 初始化读写锁
    pthread_rwlock_destroy(pthread_rwlock_t *rwlock);    // 销毁读写锁

    2. 函数原型:

    #include <pthread.h>
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 读锁
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);  // 写锁
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 解锁

    【 问题描述】程序 trainticket 中,有 100 个线程,其中 90 个线程是查余票数量的,只有 10 个线程抢票,每个线程一次买 10 张票。初始状态下一共有 1000 张票。因此执行完毕后,还会剩下 900 张票。
    程序 trainticket 在运行的时候需要传入参数,即:

        参数 0:表示不加任何锁
        参数 1:表示使用读写锁
        参数 2:表示使用互斥量

    代码实现:

     1 #include <unistd.h>
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 #include <string.h>
     5 #include <pthread.h>
     6  
     7 struct Ticket 
     8 {
     9     int remain;              // 余票数,初始化为 1000
    10     pthread_rwlock_t rwlock; // 读写锁
    11     pthread_mutex_t mlock;   // 互斥锁,主要是为了和读写锁进行对比
    12 }ticket;
    13  
    14 // 通过命令行传参数来取得这个值,用来控制到底使用哪一种锁
    15 // 0:不加锁 1:加读写锁 2:加互斥锁
    16 int lock = 0;
    17  
    18 void* query(void* arg)  //查票线程
    19 {
    20     int name = (int)arg;
    21     sleep(rand() % 5 + 1);
    22     if (lock == 1)
    23         pthread_rwlock_rdlock(&ticket.rwlock); // 读模式加锁
    24     else if (lock == 2)
    25         pthread_mutex_lock(&ticket.mlock);
    26  
    27     int remain = ticket.remain;
    28     sleep(1);
    29     printf("%03d query: %d\n", name, remain);
    30  
    31     if (lock == 1)
    32         pthread_rwlock_unlock(&ticket.rwlock);
    33     else if (lock == 2)
    34         pthread_mutex_unlock(&ticket.mlock);
    35  
    36     return NULL;
    37 }
    38  
    39  
    40 void* buy(void* arg)  // 抢票线程
    41 {
    42     int name = (int)arg;
    43  
    44     if (lock == 1)
    45         pthread_rwlock_wrlock(&ticket.rwlock); // 写模式加锁
    46     else if (lock == 2)
    47         pthread_mutex_lock(&ticket.mlock);
    48  
    49     int remain = ticket.remain;
    50     remain -= 10; // 一次买 10 张票
    51     sleep(1);
    52     ticket.remain = remain;
    53     printf("%03d buy 10 tickets\n", name);
    54  
    55     if (lock == 1)
    56         pthread_rwlock_unlock(&ticket.rwlock);
    57     else if (lock == 2)
    58         pthread_mutex_unlock(&ticket.mlock);
    59  
    60     sleep(rand() % 5 + 2);
    61     return NULL;
    62 }
    63  
    64 int main(int argc, char* argv[]) 
    65 {
    66     lock = 0;
    67     if (argc >= 2) 
    68         lock = atoi(argv[1]);
    69  
    70     int names[100];
    71     pthread_t tid[100];
    72  
    73     int i;
    74     for (i = 0; i < 100; ++i) 
    75         names[i] = i;
    76     ticket.remain = 1000;
    77     printf("remain ticket = %d\n", ticket.remain);
    78  
    79     pthread_rwlock_init(&ticket.rwlock, NULL);
    80     pthread_mutex_init(&ticket.mlock, NULL);
    81  
    82     for (i = 0; i < 100; ++i) {
    83         if (i % 10 == 0)
    84             pthread_create(&tid[i], NULL, buy, (void*)names[i]);
    85         else
    86             pthread_create(&tid[i], NULL, query, (void*)names[i]);
    87     }
    88  
    89     for (i = 0; i < 100; ++i) 
    90         pthread_join(tid[i], NULL);
    91     pthread_rwlock_destroy(&ticket.rwlock);
    92     pthread_mutex_destroy(&ticket.mlock);
    93     printf("remain ticket = %d\n", ticket.remain);
    94     return 0;
    95 }

    参考资料

    1. 面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

  • 相关阅读:
    单变量线性回归
    【记】国奖交流会
    转【研究生第一篇学术论文常犯问题总结】
    this.$confirm里面使用await异步调取接口数据
    margin和padding的值是百分比的时候是怎么计算的?
    原生js实现三级联动下拉框
    两个数组里面的对象元素根据相同的id合并到一个数组
    制作遮罩层的样式
    自定义表单验证方法的使用
    封装获取操作系统和浏览器类型的方法
  • 原文地址:https://www.cnblogs.com/sunbines/p/16138119.html
Copyright © 2020-2023  润新知