文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Linux多线程同步机制—读写锁(Read-Write Lock)

2024-11-29 20:48

关注

读写锁原理

读写锁的设计基于以下原则:

读写锁内部实现机制

读写锁的内部实现通常依赖于一个或多个底层锁和一些额外的状态信息。以下是一种常见的实现方式:

读写锁的典型实现

在 Linux 和 POSIX 兼容的系统中,读写锁通常通过 pthread_rwlock_t 类型实现。其内部可能包含如下组件:

当线程尝试获取读锁时,它会检查写锁状态和读计数器,如果当前没有写线程正在访问资源,则增加读计数器并允许读线程继续;如果存在写操作,则读线程将被阻塞,直到写操作完成。

类似地,当线程尝试获取写锁时,它会检查读计数器和写锁状态。如果当前没有读线程和写线程正在访问资源,则设置写锁状态并允许写线程继续;如果有读线程或写线程正在访问资源,则写线程将被阻塞,直到所有读线程和前一个写线程完成操作。

读写锁相关API

当然,我会补充完整上面的程序,并进一步完善API函数的描述。请注意,由于程序中的线程是无限循环的,为了示例的完整性,我将添加一个全局变量作为退出条件。此外,我将更详细地解释API函数的使用。

pthread_rwlock_init -- 初始化读写锁

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

rwlock:指向要初始化的读写锁变量的指针。

attr:(可选)指向读写锁属性的指针。如果传递NULL,则使用默认属性。

pthread_rwlock_destroy -- 销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

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

pthread_rwlock_rdlock -- 加读锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

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

pthread_rwlock_wrlock -- 加写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

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

pthread_rwlock_tryrdlock -- 尝试加读锁(非阻塞)

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

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

C 语言实现读写锁

一、封装POSIX 线程库的读写锁

封装 POSIX 线程库提供的pthread_rwlock_t类型的读写锁,以及相关的操作函数pthread_rwlock_rdlock、pthread_rwlock_wrlock等,即可实现简单的读写锁,无需自行实现复杂的逻辑。以下是一个简单的 C 语言实现读写锁的代码:

#include 
#include 
#include 
#include 

// 定义读写锁结构体
typedef struct {
    pthread_rwlock_t rwlock; // 使用 POSIX 的读写锁
} rwlock_t;

// 定义线程参数结构体
typedef struct thread_params {
    rwlock_t *lock;
    int id; // 线程标识符
} thread_params_t;

// 初始化读写锁
void rwlock_init(rwlock_t *lock) {
    pthread_rwlock_init(&lock->rwlock, NULL);
}

// 销毁读写锁并释放资源
void rwlock_destroy(rwlock_t *lock) {
    pthread_rwlock_destroy(&lock->rwlock);
}

// 获取读锁
void rwlock_read_lock(rwlock_t *lock) {
    pthread_rwlock_rdlock(&lock->rwlock);
}

// 释放读锁
void rwlock_read_unlock(rwlock_t *lock) {
    pthread_rwlock_unlock(&lock->rwlock);
}

// 获取写锁
void rwlock_write_lock(rwlock_t *lock) {
    pthread_rwlock_wrlock(&lock->rwlock);
}

// 释放写锁
void rwlock_write_unlock(rwlock_t *lock) {
    pthread_rwlock_unlock(&lock->rwlock);
}

// 读者线程函数
void *reader(void *arg) {
    thread_params_t *params = arg; // 从参数中获取线程参数结构体
    int i;
    for (i = 0; i < 3; i++) {
        rwlock_read_lock(params->lock);
        printf("读者线程 %d: 正在读取...\n", params->id);
        usleep(100000);
        rwlock_read_unlock(params->lock);
    }
    return NULL;
}

// 写者线程函数
void *writer(void *arg) {
    thread_params_t *params = arg; // 从参数中获取线程参数结构体
    int i;
    for (i = 0; i < 5; i++) {
        rwlock_write_lock(params->lock);
        printf("写者线程 %d: 正在写入...\n", params->id);
        usleep(500000);
        rwlock_write_unlock(params->lock);
    }
    return NULL;
}

int main() {
    rwlock_t lock;
    rwlock_init(&lock);

    pthread_t threads[5];
    thread_params_t thread_params[5]; // 定义线程参数数组

    int i;

    // 初始化线程参数数组
    for (i = 0; i < 5; i++) {
        thread_params[i].lock = &lock;
        thread_params[i].id = i + 1; // 分配线程标识符
    }

    // 创建读者线程
    for (i = 0; i < 4; i++) {
        pthread_create(&threads[i], NULL, reader, &thread_params[i]);
    }

    // 创建写者线程
    pthread_create(&threads[4], NULL, writer, &thread_params[4]);

    // 加入所有线程,等待它们完成
    for (i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    rwlock_destroy(&lock);

    return 0;
}

注意:封装 rwlock_t 结构体的主要原因在于提供一个清晰、模块化的接口来管理和使用读写锁,这带来了以下几方面的优势:

  1. 封装细节:

将读写锁的实现细节封装在rwlock_t结构体内,对外只暴露必要的接口(如初始化、销毁、读锁和写锁操作)。这样做的好处是隐藏了内部实现的复杂性,外部调用者只需要关注如何使用锁,而不必关心锁的具体实现。

  1. 类型安全:

使用专门的结构体类型rwlock_t来表示读写锁,增强了类型安全。这意味着在使用读写锁的地方,编译器可以检查是否正确使用了读写锁相关的函数,避免了类型错误。

  1. 可扩展性:

如果将来需要改变读写锁的实现方式,比如从使用pthread_rwlock_t切换到另一种锁机制,只需修改rwlock_t结构体和相关操作函数,而无需修改所有使用读写锁的地方。这大大提高了代码的可维护性和可扩展性。

  1. 代码组织与重用:

封装读写锁的操作在一个独立的结构体和一组函数中,使得代码更加整洁、有条理。此外,这样的封装有利于代码的重用,如果项目中其他地方也需要使用读写锁,可以直接引用rwlock_t及相关函数,无需重复编写相同的代码。

  1. 测试与调试便利性:

将读写锁的创建、使用和销毁操作集中在一个结构体及其相关函数中,方便了单元测试和调试。可以独立地测试读写锁的功能,确保其在各种情况下的正确性。

综上所述,封装rwlock_t结构体是软件工程中一种常见的抽象和封装机制,它不仅提高了代码的可读性和可维护性,也增强了系统的灵活性和健壮性。

这个示例中的关键点详细阐述如下:

1. 读写锁结构体定义 (rwlock_t)

typedef struct {
    pthread_rwlock_t rwlock; // 使用 POSIX 的读写锁
} rwlock_t;

rwlock_t结构体封装了一个pthread_rwlock_t类型的读写锁实例。

pthread_rwlock_t 是 POSIX 标准中定义的一种高级锁机制,允许同时存在多个读操作,但写操作是排他的,确保了数据在并发访问时的一致性。

2. 线程参数结构体定义 (thread_params_t)

typedef struct thread_params {
    rwlock_t *lock;
    int id; // 线程标识符
} thread_params_t;

thread_params_t 结构体用于存储线程运行所需的信息,包括指向读写锁的指针和线程的唯一标识符。

这种设计允许线程函数以统一的方式接收参数,增强代码的可读性和可维护性。

3. 读写锁操作函数

void rwlock_init(rwlock_t *lock);
void rwlock_destroy(rwlock_t *lock);

rwlock_init 负责初始化读写锁,确保其处于可用状态。

rwlock_destroy 用于清理锁资源,避免内存泄漏。

解析:

void rwlock_read_lock(rwlock_t *lock);
void rwlock_read_unlock(rwlock_t *lock);

4. 线程函数

void *reader(void *arg);

读者线程函数 reader 接收一个 thread_params_t 类型的参数,从中提取读写锁和线程标识符。

线程执行多次读操作,每次读取前获取读锁,读取后释放读锁。

解析:

void *writer(void *arg);

5. 主函数 (main)

初始化读写锁。

创建线程参数数组,为每个线程分配唯一标识符和读写锁引用。

使用pthread_create 创建 4 个读者线程和 1 个写者线程。

6. 线程标识符

每个线程拥有一个从 1 开始的唯一标识符,便于在日志和调试信息中区分不同线程。

7. POSIX读写锁机制

支持多个读线程的同时访问,提高了读密集型应用的并发性能。

确保写操作的排他性,避免数据损坏,适用于写操作较少的场景。

8. 线程创建与管理

使用pthread_create创建线程,传入线程函数和参数。

利用pthread_join等待线程结束,保证程序的正确性和资源的有序释放。

9. 资源管理

通过初始化和销毁读写锁,确保了锁资源的生命周期管理,避免了内存泄漏和资源浪费。

编译并执行程序,结果如下:

[root@localhost rwlock]# gcc pthread_rwlock.c -o pthread_rwlock -lpthread
[root@localhost rwlock]# ls
pthread_rwlock  pthread_rwlock.c
[root@localhost rwlock]# ./pthread_rwlock
读者线程 1: 正在读取...
读者线程 2: 正在读取...
读者线程 3: 正在读取...
读者线程 4: 正在读取...
读者线程 1: 正在读取...
读者线程 2: 正在读取...
读者线程 4: 正在读取...
读者线程 3: 正在读取...
读者线程 1: 正在读取...
读者线程 2: 正在读取...
读者线程 4: 正在读取...
读者线程 3: 正在读取...
写者线程 5: 正在写入...
写者线程 5: 正在写入...
写者线程 5: 正在写入...
写者线程 5: 正在写入...
写者线程 5: 正在写入...

通过深入解析上述代码和结果,我们可以更全面地理解基于 POSIX 读写锁的多线程程序设计策略,以及如何有效利用锁机制来提高并发应用的性能和可靠性。

二、自定义实现的典型读写锁

自定义实现读写锁代码需要开发者更深入地理解读写锁的底层实现原理。完全自行实现读写锁的逻辑,通过互斥锁、条件变量以及自定义的读计数和写标志来管理读写操作的同步。以下是一段 C 语言实现自定义实现读写锁的代码:

#include 
#include 

// 定义读写锁结构体
typedef struct rwlock {
    pthread_mutex_t lock;  // 互斥锁,用于保护读写锁的内部状态
    pthread_cond_t read_cond;  // 条件变量,用于读操作的等待和通知
    pthread_cond_t write_cond;  // 条件变量,用于写操作的等待和通知
    int read_count;  // 读操作的计数
    int write_in_progress;  // 写操作是否正在进行的标志
} rwlock_t;

// 初始化读写锁
void rwlock_init(rwlock_t *rwlock) {
    // 初始化互斥锁
    pthread_mutex_init(&rwlock->lock, NULL);
    // 初始化读操作的条件变量
    pthread_cond_init(&rwlock->read_cond, NULL);
    // 初始化写操作的条件变量
    pthread_cond_init(&rwlock->write_cond, NULL);
    // 初始时读计数为 0
    rwlock->read_count = 0;
    // 初始时写操作未进行
    rwlock->write_in_progress = 0;
}

// 读锁加锁
void rwlock_read_lock(rwlock_t *rwlock) {
    // 获取互斥锁
    pthread_mutex_lock(&rwlock->lock);
    // 若有写操作正在进行,读线程等待
    while (rwlock->write_in_progress) {
        pthread_cond_wait(&rwlock->read_cond, &rwlock->lock);
    }
    // 读计数增加
    rwlock->read_count++;
    // 释放互斥锁
    pthread_mutex_unlock(&rwlock->lock);
}

// 读锁解锁
void rwlock_read_unlock(rwlock_t *rwlock) {
    // 获取互斥锁
    pthread_mutex_lock(&rwlock->lock);
    // 读计数减少
    rwlock->read_count--;
    // 若读计数为 0 且无写操作正在进行,通知写线程
    if (rwlock->read_count == 0 && rwlock->write_in_progress == 0) {
        pthread_cond_signal(&rwlock->write_cond);
    }
    // 释放互斥锁
    pthread_mutex_unlock(&rwlock->lock);
}

// 写锁加锁
void rwlock_write_lock(rwlock_t *rwlock) {
    // 获取互斥锁
    pthread_mutex_lock(&rwlock->lock);
    // 若有读操作或写操作正在进行,写线程等待
    while (rwlock->read_count > 0 || rwlock->write_in_progress) {
        pthread_cond_wait(&rwlock->write_cond, &rwlock->lock);
    }
    // 标记写操作正在进行
    rwlock->write_in_progress = 1;
    // 释放互斥锁
    pthread_mutex_unlock(&rwlock->lock);
}

// 写锁解锁
void rwlock_write_unlock(rwlock_t *rwlock) {
    // 获取互斥锁
    pthread_mutex_lock(&rwlock->lock);
    // 标记写操作结束
    rwlock->write_in_progress = 0;
    // 通知所有等待读的线程
    pthread_cond_broadcast(&rwlock->read_cond);
    // 通知等待写的线程
    pthread_cond_signal(&rwlock->write_cond);
    // 释放互斥锁
    pthread_mutex_unlock(&rwlock->lock);
}

// 读线程的操作函数
void reader_function(rwlock_t *rwlock) {
    rwlock_read_lock(rwlock);
    printf("Reader is reading...\n");
    rwlock_read_unlock(rwlock);
}

// 写线程的操作函数
void writer_function(rwlock_t *rwlock) {
    rwlock_write_lock(rwlock);
    printf("Writer is writing...\n");
    rwlock_write_unlock(rwlock);
}

int main() {
    rwlock_t rwlock;  // 定义读写锁变量
    rwlock_init(&rwlock);  // 初始化读写锁

    pthread_t reader1, reader2, writer;  // 定义线程变量

    // 创建读线程 1
    pthread_create(&reader1, NULL, (void *)reader_function, &rwlock);
    // 创建读线程 2
    pthread_create(&reader2, NULL, (void *)reader_function, &rwlock);
    // 创建写线程
    pthread_create(&writer, NULL, (void *)writer_function, &rwlock);

    // 等待读线程 1 结束
    pthread_join(reader1, NULL);
    // 等待读线程 2 结束
    pthread_join(reader2, NULL);
    // 等待写线程结束
    pthread_join(writer, NULL);

    return 0;
}

以下是对这段代码的详细解析:

包含头文件

#include :包含了 POSIX 线程库的头文件,用于多线程编程。

#include :包含了标准输入输出头文件,用于打印输出。

定义读写锁结构体 rwlock_t

函数定义

4.main 函数

编译并执行程序,结果如下:

[root@localhost rwlock]# gcc pthread_rwlock_st.c -o pthread_rwlock_st -lpthread
[root@localhost rwlock]# ls
pthread_rwlock  pthread_rwlock.c  pthread_rwlock_st  pthread_rwlock_st.c
[root@localhost rwlock]# ./pthread_rwlock_st
Reader is reading...
Reader is reading...
Writer is writing...

总结

读写锁相比于传统的互斥锁(mutex)具有更高的并发性能,特别是在读操作远多于写操作的场景下。这是因为读写锁允许多个读线程同时访问共享资源,从而减少了线程间的等待时间,提高了系统的整体吞吐量。

然而,读写锁也有其局限性,例如在写操作频繁的场景下,由于写操作的排他性,可能会导致大量的读线程被阻塞,降低系统的并发性能。因此,在设计多线程应用时,选择合适的同步机制是非常重要的。

来源:Linux二进制内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯