Linux多线程实践(6) --Posix读写锁解决读者写者问题

简介: Posix读写锁int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, ...

Posix读写锁

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_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

读写锁与互斥量类似, 不过读写锁允许更高的并行性. 读写锁用于读称为共享锁, 读写锁用于写称为排它锁;

读写锁规则:

  只要没有线程持有给定的读写锁用于写, 那么任意数目的线程可以持有读写锁用于读;

  仅当没有线程持有某个给定的读写锁用于读或用于写时, 才能分配读写锁用于写;

 

Posix自旋锁

int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

int pthread_spin_unlock(pthread_spinlock_t *lock);

  自旋锁类似于互斥锁, 它的性能比互斥锁更高;

  自旋锁与互斥锁很重要的一个区别在于: 线程在申请自旋锁的时候, 线程并不会挂起, 它总是处于忙等待的状态(一直在自旋, CPU处于空耗的状态);

  自旋锁可用于以下情况:锁被持有的时间短, 而且线程并不希望在重新调度上花费太多的成本.

  自旋锁通常作为底层原语用于实现其他类型的锁: 比如有些互斥锁的实现在试图获取互斥量的时候会自旋一小段时间, 只有在自旋计数到达某一阈值的时候才会休眠; 因此, 很多互斥量的实现非常搞笑, 以至于应用程序采用互斥锁的性能与曾经采用过自旋锁的性能基本上是相同的.

  因此, 自旋锁只在某些特定的情况下有用, 比如在用户层, 自旋锁并不是非常有用, 除非运行在不允许抢占的实时调度类中.

 

读者写者问题

问题描述

  一个数据对象可以为多个并发进程所共享。其中有的进程可能只需要读共享对象的内容,而其他进程可能要更新共享对象的内容。

    读者:只对读感兴趣的进程;

    写者:其他进程(只写,或既读又写);

  规则

    允许多个读者同时读取数据;

    只有一个写者可以写数据;

    写者在写时读者不能读,反之亦然。

/** 实现1: 运用读写锁解决”读者写者问题”
解题思路: 将需要读写的文件实现为一个字符串;
读者进程: 一次可以将该字符串全部读出, 然后打印读取信息
写者进程: 一次只能修改一个字符(该字符从A~Z循环写入), 修改之后打印写入信息
**/
//读写锁
pthread_rwlock_t rwlock;
const unsigned READERCOUNT = 2; //读者数
const unsigned WRITERCONUT = 5; //写者数

const int PAPERSIZE = 32;       //文件长度
char paper[PAPERSIZE+1];        //文件

unsigned short int write_index = 0; //写者需要写入的位置
char ch = 'A';  //写者需要写入的字母

pthread_t thread[READERCOUNT+WRITERCONUT];  //读者+写者线程

//读者线程
void *reader(void *args)
{
    int number = *(int *)args;
    delete (int *)args;

    while (true)
    {
        //获取共享锁
        pthread_rwlock_rdlock(&rwlock);
        //开始读
        printf("## reader %d was reading...\n", number);
        printf("text: %s\n", paper);
        printf("   reader %d end reading...\n", number);
        //解锁共享锁
        pthread_rwlock_unlock(&rwlock);

        sleep(1);
    }
    pthread_exit(NULL);
}
//写者线程
void *writer(void *args)
{
    int number = *(int *)args;
    delete (int *)args;
    while (true)
    {
        //获取写锁
        pthread_rwlock_wrlock(&rwlock);
        //开始写
        printf("++ writer %d was writing...\n", number);
        paper[write_index] = ch;
        write_index = (write_index+1)%PAPERSIZE;
        ch = ch+1;
        if (ch > 'Z')
            ch = 'A';
        printf("   writer %d end writing...\n", number);
        //释放写锁
        pthread_rwlock_unlock(&rwlock);

        sleep(1);
    }

    pthread_exit(NULL);
}

int main()
{
    memset(paper, 0, sizeof(paper));
    pthread_rwlock_init(&rwlock, NULL);

    for (unsigned int i = 0; i < READERCOUNT; ++i)
        pthread_create(&thread[i], NULL, reader, new int(i));
    for (unsigned int i = 0; i < WRITERCONUT; ++i)
        pthread_create(&thread[READERCOUNT+i], NULL, writer, new int(i));
    for (unsigned int i = 0; i < READERCOUNT+WRITERCONUT; ++i)
        pthread_join(thread[i], NULL);

    pthread_rwlock_destroy(&rwlock);
}

/** 实现2: 运用Posix信号量使用”读者优先”策略解决”读者写者问题”
解题思路:
如果新读者到:
   ①无读者、写者,新读者可以读;
   ②有写者等待,但有其它读者正在读,则新读者也可以读;
   ③有写者写,新读者等待。
如果新写者到:
   ①无读者,新写者可以写;
   ②有读者,新写者等待;
   ③有其它写者,新写者等待。
**/
// 需要用两个互斥量实现
pthread_mutex_t rmutex;
pthread_mutex_t wmutex;

const unsigned READERCOUNT = 5; //读者数
const unsigned WRITERCONUT = 5; //写者数

const int PAPERSIZE = 32;       //文件长度
char paper[PAPERSIZE+1];        //文件

unsigned short int write_index = 0; //写者需要写入的位置
char ch = 'A';  //写者需要写入的字母

pthread_t thread[READERCOUNT+WRITERCONUT];  //读者+写者线程

int nReader = 0;
//读者线程
void *reader(void *args)
{
    int number = *(int *)args;
    delete (int *)args;

    while (true)
    {
        pthread_mutex_lock(&rmutex);
        //如果是第一个读者, 则锁定wmutex
        if (nReader == 0)
            pthread_mutex_lock(&wmutex);
        ++ nReader;
        pthread_mutex_unlock(&rmutex);

        //开始读
        printf("## reader %d was reading...\n", number);
        printf("text: %s\n", paper);
        printf("   reader %d end reading...\n\n", number);

        pthread_mutex_lock(&rmutex);
        -- nReader;
        //如果是最后一个读者, 则解锁wmutex
        if (nReader == 0)
            pthread_mutex_unlock(&wmutex);
        pthread_mutex_unlock(&rmutex);

        sleep(1);
    }
    pthread_exit(NULL);
}

//写者线程
void *writer(void *args)
{
    int number = *(int *)args;
    delete (int *)args;
    while (true)
    {
        //获取写锁
        pthread_mutex_lock(&wmutex);
        //开始写
        printf("++ writer %d was writing...\n", number);
        paper[write_index] = ch;
        write_index = (write_index+1)%PAPERSIZE;
        ch = ch+1;
        if (ch > 'Z')
            ch = 'A';
        printf("   writer %d end writing...\n\n", number);
        //释放写锁
        pthread_mutex_unlock(&wmutex);

        sleep(1);
    }

    pthread_exit(NULL);
}

int main()
{
    memset(paper, 0, sizeof(paper));
    pthread_mutex_init(&rmutex, NULL);
    pthread_mutex_init(&wmutex, NULL);

    for (unsigned int i = 0; i < READERCOUNT; ++i)
        pthread_create(&thread[i], NULL, reader, new int(i));
    for (unsigned int i = 0; i < WRITERCONUT; ++i)
        pthread_create(&thread[READERCOUNT+i], NULL, writer, new int(i));
    for (unsigned int i = 0; i < READERCOUNT+WRITERCONUT; ++i)
        pthread_join(thread[i], NULL);

    pthread_mutex_destroy(&rmutex);
    pthread_mutex_destroy(&wmutex);
}

  “读者优先”思想小结: 读者优先的设计思想是读进程只要看到有其它读进程正在读,就可以继续进行读;写进程必须等待所有读进程都不读时才能写,即使写进程可能比一些读进程更早提出申请。该算法只要还有一个读者在活动,就允许后续的读者进来,该策略的结果是,如果有一个稳定的读者流存在,那么这些读者将在到达后被允许进入。而写者就始终被挂起,直到没有读者为止.

目录
相关文章
|
9月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
7月前
|
Linux C语言
Linux读写锁源码分析
本文分析了读写锁的实现原理与应用场景,基于glibc 2.17源码。读写锁通过读引用计数、写线程ID、条件变量等实现,支持读优先(默认)和写优先模式。读优先时,写锁可能饥饿;写优先时,读线程需等待写锁释放。详细解析了`pthread_rwlock_t`数据结构及加解锁流程,并通过实验验证:2000个读线程与1个写线程测试下,读优先导致写锁饥饿,写优先则正常抢占锁。
182 19
linux内核 —— 读写信号量实验
linux内核 —— 读写信号量实验
|
运维 监控 Linux
Linux系统读写硬盘慢,如何排查?四步教你定位问题!
【8月更文挑战第24天】在Linux系统运维中,硬盘读写速度慢是一个常见且影响重大的问题。它不仅会导致服务器性能下降,还会影响用户体验。本文将详细介绍四个步骤,帮助你快速定位并解决Linux系统硬盘读写慢的问题。
1992 0
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
205 0
|
存储 Linux 调度
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
168 0
|
27天前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
114 6
|
4月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
282 83
|
15天前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
155 0
|
2月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
170 16

热门文章

最新文章