嵌入式Linux:线程同步(读写锁)

目录

1、读写锁的初始化

2、销毁读写锁

3、读写锁加锁与解锁

4、读写锁的属性


在Linux中,读写锁(Read-Write Lock)提供了一种同步机制,允许多个线程并发读取共享资源,但只有一个线程可以对该资源进行写操作。读写锁相比互斥锁(mutex)或自旋锁(spinlock)具有更高的并行性,因为它有三种状态:读加锁状态、写加锁状态和不加锁状态。

读写锁的规则和状态:

  • 写模式加锁状态:当一个线程获取写锁时,其他所有试图获取该锁的线程(无论是读锁还是写锁)都会被阻塞,直到写锁被释放。

  • 读模式加锁状态:当线程获取读锁时,其他试图获取读锁的线程可以并发成功获取锁,但任何试图获取写锁的线程会被阻塞,直到所有读锁被释放。

读写锁的使用场景:

  • 适用于读操作频繁且写操作较少的情况,这样能够允许多线程并发读取,减少锁的竞争,提高系统的效率。
  • 当需要保护一个共享数据结构,同时支持多个线程读,但限制只有一个线程写时,读写锁是比简单的互斥锁更好的选择。

1、读写锁的初始化

在使用读写锁之前,必须对其进行初始化。Linux使用pthread_rwlock_t数据类型来表示读写锁,初始化方式有以下两种:

静态初始化

cpp 复制代码
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

动态初始化 : 使用pthread_rwlock_init()函数初始化:

cpp 复制代码
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

参数说明

  • rwlock:指向需要初始化的读写锁对象(类型为pthread_rwlock_t)。
  • attr:指向读写锁属性的指针(类型为pthread_rwlockattr_t),可以设置为NULL,表示使用默认属性。

返回值

  • 成功返回0
  • 失败返回非0错误码,如:
    • EINVAL:表示无效的属性值或锁对象。
    • EBUSY:锁已初始化。
    • ENOMEM:系统内存不足。

2、销毁读写锁

当不再需要使用读写锁时,应该使用pthread_rwlock_destroy()销毁它。函数原型如下:

cpp 复制代码
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要销毁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回非0错误码,如:EBUSY:锁被其他线程持有。

3、读写锁加锁与解锁

以读模式加锁,该函数会阻塞调用线程,直到能够成功获取读锁。如果已经有其他线程持有写锁,当前线程将会等待。

cpp 复制代码
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要加锁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回非0错误码,如:
    • EINVAL:无效的锁。
    • EDEADLK:检测到死锁。
    • EAGAIN:系统无法分配更多的读锁。

以写模式加锁,该函数会阻塞调用线程,直到能够成功获取写锁。只有当前线程能够获取写锁,其他所有请求锁的线程(无论是读锁还是写锁)都会被阻塞。

cpp 复制代码
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要加锁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回非0错误码,如:
    • EINVAL:无效的锁。
    • EDEADLK:检测到死锁。

尝试获取读锁,该函数尝试获取读锁,不会阻塞。如果锁被其他线程占用,立即返回失败。

cpp 复制代码
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要加锁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回EBUSY表示锁已被占用,当前无法获取。

尝试获取写锁,该函数尝试获取写锁,不会阻塞。如果锁被其他线程占用,立即返回失败。

cpp 复制代码
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要加锁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回EBUSY表示锁已被占用,当前无法获取。

该函数用于释放当前线程持有的锁,无论是读锁还是写锁。

cpp 复制代码
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

参数说明

  • rwlock:指向需要解锁的读写锁对象。

返回值

  • 成功返回0
  • 失败返回非0错误码,如:
    • EINVAL:无效的锁对象。
    • EPERM:当前线程未持有该锁。

4、读写锁的属性

读写锁也可以有属性,使用pthread_rwlockattr_t数据类型来表示。

初始化读写锁属性函数原型:

cpp 复制代码
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

参数说明

  • attr:指向需要初始化的读写锁属性对象。

返回值

  • 成功返回0
  • 失败返回非0错误码。

读写锁的属性中最常见的是进程共享属性,使用以下函数设置或获取:

cpp 复制代码
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

参数说明

  • attr:指向需要设置的读写锁属性对象。
  • pshared:共享属性的值。取值如下:
    • PTHREAD_PROCESS_SHARED:允许多个进程共享该读写锁。
    • PTHREAD_PROCESS_PRIVATE:仅限当前进程的线程共享读写锁(默认值)。

返回值

  • 成功返回0
  • 失败返回非0错误码。

以下代码展示了如何在读写锁的保护下,允许多个线程并发读取共享资源,但只有一个线程可以修改它:

cpp 复制代码
#include <pthread.h>
#include <stdio.h>

pthread_rwlock_t rwlock;
int shared_data = 0;

void *reader(void *arg) {
    pthread_rwlock_rdlock(&rwlock);  // 获取读锁
    printf("Reader: Shared data is %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);  // 解锁
    return NULL;
}

void *writer(void *arg) {
    pthread_rwlock_wrlock(&rwlock);  // 获取写锁
    shared_data += 1;
    printf("Writer: Updated shared data to %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);  // 解锁
    return NULL;
}

int main() {
    pthread_t r1, r2, w1;

    pthread_rwlock_init(&rwlock, NULL);  // 初始化读写锁

    pthread_create(&r1, NULL, reader, NULL);
    pthread_create(&w1, NULL, writer, NULL);
    pthread_create(&r2, NULL, reader, NULL);

    pthread_join(r1, NULL);
    pthread_join(w1, NULL);
    pthread_join(r2, NULL);

    pthread_rwlock_destroy(&rwlock);  // 销毁读写锁
    return 0;
}

Linux中的读写锁适用于提高读密集型应用的并发性。它能够让多个线程同时读取资源,从而减少锁争用,但也需要合理考虑写饥饿问题。注意事项如下:

  • 读写锁的潜在问题:如果读操作过于频繁,可能导致写线程长时间无法获取写锁,从而引发写饥饿问题。这通常需要通过其他机制(如优先级)来控制。
  • 使用场景:当读操作远多于写操作时,读写锁能带来性能提升。如果写操作频繁,读写锁可能并不会比互斥锁表现更好。
相关推荐
x-cmd4 小时前
Mac 涨价后,本地 AI 还能千元入门吗?
linux·人工智能·macos·ai·agent·amd·本地ai入门
潘潘的嵌入式日记5 小时前
改完Keil工程文件被缓存覆盖?——uvprojx编辑的进阶四坑
xml·嵌入式·keil·调试·mdk·工程文件
ICECREAM9 小时前
嵌入式 —— 串口与 UART 深入详解 —— i.MX6ULL 实战
嵌入式
摇滚侠10 小时前
Linux 零基础教程 09、11、77
linux·运维·服务器
嵌入式学习和实践13 小时前
嵌入式 Linux 调闭源 SDK 怕崩?用 dlopen 把第三方动态库库“隔离“起来
linux·三方库
小樱花的樱花14 小时前
Linux 多线程编程:互斥锁(Mutex)详解
linux·c语言·开发语言
麦兜和小可的舅舅14 小时前
从原理到实战:Linux 系统性能诊断核心指标全解析及生产系统故障分析复盘
大数据·linux·运维
浅止菌14 小时前
嵌入式Linux内存优化:从OOM到稳定运行的6个实战技巧
嵌入式
大辉狼_音频架构14 小时前
Vol.05 VendorHAL-NXP
嵌入式·音视频开发
mounter62515 小时前
质检员与超能引擎的碰撞:KASAN 护航 eBPF JIT 的技术演进与安全抉择
linux·ebpf·linux kernel·kernel·kasan