当前位置: 首页 > news >正文

Linux中读写锁详细介绍

读写锁介绍

Linux 中的读写锁(Read-Write Lock)是一种用于线程同步的机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这种机制在读操作远多于写操作的场景下,可以显著提高并发性能。读写锁主要有以下特点

读写锁区分了对共享资源的读取和写入操作。这使得它可以根据不同的操作类型,采用不同的锁定策略。

对于读锁(共享)来说,允许多个线程同时持有,用于保护读取操作,当有线程持有读锁时,其他线程也可以获取读锁

对于写锁(独占)来说,一次只能有一个线程持有,用于保护写入操作,当有线程持有写锁时,其他线程都不能获取读锁或者写锁

想要获取读锁和写锁的要求

        一个线程想要成功获取某资源的读锁,要求其他线程不持有任何锁或者只持有读锁可以获取成功,如果其他线程持有该资源写锁的话就获取不成功

        一个线程想要成功获取某资源的写锁,要求其他线程不持有任何锁才可以获取成功,如果其他线程持有该资源写锁或者该资源读锁的话就获取不成功

读写锁的适用情况

读写锁的优点

  • 提高读操作并发性:在读多写少的场景下,读写锁可以显著提高读取操作的并发性,从而提高程序的整体性能。
  • 减少线程阻塞:与互斥锁相比,读写锁减少了线程因读取操作而阻塞的概率,这提高了线程的利用率和程序的响应速度。


读写锁的适用情况

  • 读多写少的共享资源:
    • 例如,配置文件、数据缓存、查找表等。
  • 需要频繁读取但很少修改的数据:
    • 例如,传感器数据、实时监控数据等。


互斥锁VS读写锁

互斥锁确保了在任何时刻只有一个线程可以访问受保护的资源,无论是读取还是写入

读写锁的适合使用在在读操作远多于写操作的场景下,它允许多个线程同时读取共享资源,提高了读操作的并发性,但只允许一个线程写入,对于资源写操作没有提高。

读写锁的使用流程

  1. 初始化读写锁: 使用pthread_rwlock_init()创建读写锁。
  2. 获取锁:

    读取共享资源前,使用pthread_rwlock_rdlock()获取读锁。

    写入共享资源前,使用pthread_rwlock_wrlock()获取写锁。

  3. 访问共享资源: 在读写锁的保护下,安全地访问共享资源。
  4. 解锁读写锁: 在完成共享资源访问后,使用pthread_rwlock_unlock()解锁读写锁。
  5. 销毁读写锁: 在读写锁不再使用时,使用pthread_rwlock_destroy()销毁它。

pthread_rwlock_init():初始化读写锁

函数作用:初始化一个读写锁。

函数原型:int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

参数

  • rwlock:指向要初始化的读写锁变量的指针。
  • attr:读写锁属性,通常设置为NULL表示使用默认属性。

返回值

成功返回0,失败返回错误码。

注意

  • 为了让多个线程都能使用同一个读写锁,读写锁变量必须定义于线程可以共享访问的内存区域,比如全局变量静态变量或者堆上,读写锁变量的类型为pthread_rwlock_t
  • 使用读写锁的第一步必须是初始化读写锁,不初始化可能会出现未定义情况。
  • 通常来说pthread_rwlock_init函数的第二个参数attr我们会设置为NULL,此时会创建一个默认属性的读写锁,完全已经够用。

pthread_rwlock_rdlock():获取读锁

函数作用:获取读锁。

函数原型:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

参数

rwlock:指向要获取读锁的读写锁变量的指针。

返回值

成功返回0,失败返回错误码。

注意

  • 如果当前没有线程持有写锁,调用线程将立即获得读锁
  • 如果当前有线程持有写锁,调用线程将被阻塞,直到写锁被释放。

pthread_rwlock_tryrdlock():尝试获取读锁

函数作用:尝试获取读锁,如果读写锁已被写锁定,则不阻塞,立即返回错误。

函数原型:int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

参数

rwlock:指向要尝试获取读锁的读写锁变量的指针。

返回值

  • 成功锁定返回0。
  • 如果读写锁已被写锁定,返回EBUSY
  • 其他错误返回相应的错误码。

注意

  • pthread_rwlock_tryrdlock()是非阻塞的,它不会使调用线程进入等待状态。
  • 这个函数在需要非阻塞的锁定操作时很有用。

pthread_rwlock_wrlock():获取写锁

函数作用:获取写锁。

函数原型:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

参数

rwlock:指向要获取写锁的读写锁变量的指针。

返回值

成功返回0,失败返回错误码。

注意

  • 如果当前没有线程持有读锁或写锁,调用线程将立即获得写锁
  • 如果当前有线程持有读锁写锁,调用线程将被阻塞,直到所有锁都被释放。

pthread_rwlock_trywrlock():尝试获取写锁

函数作用:尝试获取写锁,如果读写锁已被读锁定写锁定,则不阻塞,立即返回错误。

函数原型:int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

参数

rwlock:指向要尝试获取写锁的读写锁变量的指针。

返回值

  • 成功锁定返回0。
  • 如果读写锁已被读锁定写锁定,返回EBUSY
  • 其他错误返回相应的错误码。

注意

  • pthread_rwlock_trywrlock()是非阻塞的,它不会使调用线程进入等待状态。
  • 这个函数在需要非阻塞的锁定操作时很有用。

pthread_rwlock_unlock():解锁读写锁

函数作用:解锁读写锁

函数原型:int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

参数

rwlock:指向要解锁的读写锁变量的指针。

返回值

成功返回0,失败返回错误码。

注意

  • 解锁读写锁后,其他等待该读写锁的线程可以获得锁。
  • 持有读锁或写锁的线程都可以使用这个函数解锁

一个线程对于同一个读写锁,在同一时间点上,只能拥有读锁或者写锁,不能同时拥有。

所以使用pthread_rwlock_unlock()进行解锁时,不需要指定解的是读锁还是写锁。操作系统内部会维护读写锁的状态,并且根据内部状态来确定如何解锁,这样设计简化了读写锁的使用

pthread_rwlock_destroy():销毁读写锁

函数作用:销毁一个读写锁。

函数原型:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

参数

rwlock:指向要销毁的读写锁变量的指针。

返回值

成功返回0,失败返回错误码。

注意

  • 在读写锁不再使用时,应该及时销毁它以释放资源。
  • 只有未被任何线程锁定的读写锁才能被销毁

读写锁使用示例

示例:共享数据缓存

假设我们有一个共享的数据缓存,多个线程需要读取缓存中的数据,而只有少数线程需要更新缓存。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_READERS 5
#define NUM_WRITERS 2

// 共享数据缓存
int data_cache = 0;
// 读写锁
pthread_rwlock_t rwlock;

// 读线程函数
void *reader_thread(void *arg) {
    int thread_id = *(int *)arg;

    // 获取读锁
    pthread_rwlock_rdlock(&rwlock);

    // 读取共享数据
    printf("Reader %d: Reading data_cache = %d\n", thread_id, data_cache);
    sleep(1); // 模拟耗时读取操作

    // 释放读锁
    pthread_rwlock_unlock(&rwlock);

    pthread_exit(NULL);
}

// 写线程函数
void *writer_thread(void *arg) {
    int thread_id = *(int *)arg;

    // 获取写锁
    pthread_rwlock_wrlock(&rwlock);

    // 更新共享数据
    data_cache++;
    printf("Writer %d: Updated data_cache = %d\n", thread_id, data_cache);
    sleep(2); // 模拟耗时写入操作

    // 释放写锁
    pthread_rwlock_unlock(&rwlock);

    pthread_exit(NULL);
}

int main() {
    pthread_t readers[NUM_READERS];
    pthread_t writers[NUM_WRITERS];
    int reader_ids[NUM_READERS];
    int writer_ids[NUM_WRITERS];

    // 初始化读写锁
    if (pthread_rwlock_init(&rwlock, NULL) != 0) {
        perror("pthread_rwlock_init");
        exit(EXIT_FAILURE);
    }

    // 创建读线程
    for (int i = 0; i < NUM_READERS; i++) {
        reader_ids[i] = i;
        if (pthread_create(&readers[i], NULL, reader_thread, &reader_ids[i]) != 0) {
            perror("pthread_create (reader)");
            exit(EXIT_FAILURE);
        }
    }

    // 创建写线程
    for (int i = 0; i < NUM_WRITERS; i++) {
        writer_ids[i] = i;
        if (pthread_create(&writers[i], NULL, writer_thread, &writer_ids[i]) != 0) {
            perror("pthread_create (writer)");
            exit(EXIT_FAILURE);
        }
    }

    // 等待读线程结束
    for (int i = 0; i < NUM_READERS; i++) {
        pthread_join(readers[i], NULL);
    }

    // 等待写线程结束
    for (int i = 0; i < NUM_WRITERS; i++) {
        pthread_join(writers[i], NULL);
    }

    // 销毁读写锁
    if (pthread_rwlock_destroy(&rwlock) != 0) {
        perror("pthread_rwlock_destroy");
        exit(EXIT_FAILURE);
    }

    return 0;
}

代码解释:

  1. 包含头文件:
    • stdio.h:用于输入输出。
    • stdlib.h:用于标准库函数。
    • pthread.h:用于线程相关函数。
    • unistd.h:用于sleep()函数。
  2. 定义共享数据和读写锁:
    • data_cache:一个整数变量,作为共享数据缓存。
    • rwlock:一个pthread_rwlock_t类型的变量,用于读写锁。
  3. 读线程函数reader_thread()
    • 每个读线程执行这个函数。
    • 首先,使用pthread_rwlock_rdlock()获取读锁。
    • 然后,读取共享数据data_cache
    • 使用sleep()模拟耗时读取操作。
    • 最后,使用pthread_rwlock_unlock()释放读锁。
  4. 写线程函数writer_thread()
    • 每个写线程执行这个函数。
    • 首先,使用pthread_rwlock_wrlock()获取写锁。
    • 然后,更新共享数据data_cache
    • 使用sleep()模拟耗时写入操作。
    • 最后,使用pthread_rwlock_unlock()释放写锁。
  5. main()函数:
    • 初始化读写锁:使用pthread_rwlock_init()初始化读写锁。
    • 创建读线程:创建多个读线程,并传递线程ID作为参数。
    • 创建写线程:创建多个写线程,并传递线程ID作为参数。
    • 等待线程结束:使用pthread_join()等待所有线程结束。
    • 销毁读写锁:使用pthread_rwlock_destroy()销毁读写锁。

程序输出显示读线程并发读取共享数据的过程,以及写线程更新共享数据的过程。由于读写锁的保护,读线程可以并发执行,而写线程的更新操作是互斥的。

相关文章:

  • 数学建模:MATLAB极限学习机解决回归问题
  • 整流桥选型关注参数
  • 卫星网络仿真平台:IPLOOK赋能空天地一体化通信新生态​
  • 排序大合集之冒泡
  • Hive-04之存储格式、SerDe、企业级调优
  • 记录一次FastDFS内部文件迁移过程
  • Rust配置开发环境+服务器实战
  • MTCNN 的原理
  • LeetCode 148:排序链表 (Sort Linked List)
  • C++基础知识(六)之STL容器
  • Hive之正则表达式RLIKE详解及示例
  • [Computer Vision]实验五:SFM
  • electron-builder打包时github包下载失败【解决办法】
  • 分布式微服务系统架构第92集:智能健康监测设备Java开发方案
  • RJ45网口 与 M12连接器对比(D-code,X-code)
  • 哈希碰撞攻防战——深入浅出Map/Set的底层实现
  • 2025.3.2机器学习笔记:PINN文献阅读
  • uniapp 系统学习,从入门到实战(七)—— 网络请求与数据交互
  • 多镜头视频生成、机器人抓取、扩散模型个性化 | Big Model weekly第58期
  • (KTransformers) RTX4090单卡运行 DeepSeek-R1 671B
  • 源码网站怎么做/湖南企业竞价优化公司
  • 网上营销渠道/网站seo关键词
  • 黄州做网站的/2021十大网络舆情案例
  • 白城网站建设公司/免费行情网站
  • 网站开发达成口头协议算不算诈骗/国外搜索引擎有哪些
  • 重庆 手机网站制作/网络安全