进程同步与互斥——读者/写者问题源码实现(reader-writer lock)

传送门:

进程同步与互斥——信号量(实现锁、条件变量)

进程同步与互斥——哲学家就餐问题源码实现(dining philosopher’s problem)

进程同步与互斥——读者/写者问题源码实现(reader-writer lock)

进程同步与互斥——生产者/消费者(有界缓冲区)问题源码实现

进程同步与互斥——吸烟者问题源码实现(cigarette smoker’s problem)

进程同步与互斥——理发师问题源码实现(sleeping barber problem)

进程同步与互斥——相关问题汇总(源码+伪代码)

摘自:操作系统导论

读者——写者锁

另一个经典问题源于对更加灵活的锁定原语的渴望,它承认不同的数据结构访问可能需要不同类型的锁。

例如,一个并发链表有很多插入和查找操作。插入操作会修改链表的状态(因此传统的临界区有用),而查找操作只是读取该结构,只要没有进行插入操作,我们可以并发的执行多个查找操作。读者—写者锁(reader-writer lock)就是用来完成这种操的。

typedef struct _rwlock_t 
{
    sem_t lock;        // binary semaphore 
    sem_t writelock;   // used to allow ONE writer or MANY readers
    int   readers;     // cout of readers reading in critical section
}rwlock_t;

void rwlock_init(rwlock_t* rw)
{
    rw->readers = 0;
    sem_init(&rw->lock, 0, 1);
    sem_init(&rw->writelock, 0, 1);
}

void rwlock_acquire_writelock(rwlock_t* rw)
{
    sem_wait(&rw->writelock);
}

void rwlock_release_writelock(rwlock_t* rw)
{
    sem_post(&rw->writelock);
}

void rwlock_acquire_readlock(rwlock_t* rw)
{
    sem_wait(&rw->lock);
    rw->readers++;
    if(rw->readers == 1)
    {
        sem_wait(&rw->writelock);     // first reader acquires writelock
    }
    sem_post(&rw->lock);
}

void rwlock_release_readlock(rwlock_t* rw)
{
    sem_wait(&rw->lock);
    rw->readers--;
    if(rw->readers == 0)
    {
        sem_post(&rw->writelock);    // last reader releases writelock
    }
    sem_post(&rw->lock);
}

代码很简单。如果某个线程要更新数据结构,需要调用rwlock_acquire_writelock()获得写锁,调用rwlock_release_writelock()释放锁。内部通过一个writelock的信号量保证只有一个写者能获得锁进入临界区,从而更新数据结构。

读锁的获取和释放操作更加吸引人。获取读锁时,读者首先要获取lock,然后增加reader变量,追踪目前有多少个读者在访问该数据结构。重要的步骤然后在rwlock_acquire_readlock()内发生,当第一个读者获取该锁时。在这种情况下,读者也会获取写锁,即在writelock信号量上调用sem_wait(),最后调用sem_post()释放lock。

一旦一个读者获得了读锁,其他的读者也可以获取这个读锁。但是,想要获取写锁的线程,就必须等到所有的读者都结束。最后一个退出的写者在“writelock”信号量上调用sem_post(),从而让等待的写者能够获取该锁

这一方案可行(符合预期),但有一些缺陷,尤其是公平性。读者很容易饿死写者。存在复杂一些的解决方案,也许你可以想到更好的实现?提示:有写者等待时,如何能够避免更多的读者进入并持有锁。

最后,应该指出,读者-写者锁还有一些注意点。它们通常加入了更多开锁(尤其是更复杂的实现),因此和其他一些简单快速的锁相比,读者写者锁在性能方面没有优势。无论哪种方式,它们都再次展示了如何以有趣、有用的方式来使用信号量。

源码实现:

#include <semaphore.h>
#include <unistd.h>
#include <iostream>

typedef struct _rwlock_t 
{
    sem_t lock;        // binary semaphore 
    sem_t writelock;   // used to allow ONE writer or MANY readers
    int   readers;     // cout of readers reading in critical section
}rwlock_t;

void rwlock_init(rwlock_t* rw)
{
    rw->readers = 0;
    sem_init(&rw->lock, 0, 1);
    sem_init(&rw->writelock, 0, 1);
}

void rwlock_acquire_writelock(rwlock_t* rw)
{
    sem_wait(&rw->writelock);
}

void rwlock_release_writelock(rwlock_t* rw)
{
    sem_post(&rw->writelock);
}

void rwlock_acquire_readlock(rwlock_t* rw)
{
    sem_wait(&rw->lock);
    rw->readers++;
    std::cout << "缓冲区中读者的数量: " << rw->readers << std::endl;
    if(rw->readers == 1)
    {
        std::cout << "第一个读者拿到写锁,读的过程中禁止写入数据" << std::endl;
        sem_wait(&rw->writelock);     // first reader acquires writelock
    }
    sem_post(&rw->lock);
}

void rwlock_release_readlock(rwlock_t* rw)
{
    sem_wait(&rw->lock);
    rw->readers--;
    if(rw->readers == 0)
    {
        sem_post(&rw->writelock);    // last reader releases writelock
        std::cout << "最后一个读者离开缓冲区,释放掉写锁,可以写入数据" << std::endl;
    }
    sem_post(&rw->lock);
}

void* write_process(void* arg)
{
    rwlock_t* rw = (rwlock_t*)arg;
    while(1)
    {
        rwlock_acquire_writelock(rw);
        std::cout << "向缓冲区写数据" << std::endl;
        rwlock_release_writelock(rw);
        sleep(2);
    }
}

#define loops 10

void* read_process(void* arg)
{
    rwlock_t* rw = (rwlock_t*)arg;
    /* for(int i = 0; i < loops; i++) */
    /* { */
        rwlock_acquire_readlock(rw);
        std::cout << "从缓冲区读数据" << std::endl;
        rwlock_release_readlock(rw);
    /* } */
    pthread_exit(0);
}

int main()
{
    pthread_t p_writer;
    pthread_t p_readers[10];
    
    rwlock_t rw;

    rwlock_init(&rw);

    pthread_create(&p_writer, nullptr, write_process, &rw);
    
    for(int i = 0; i < loops; i++)
    {
        pthread_create(&p_readers[i], nullptr, read_process, &rw);
        sleep(1);
    }

    /* pthread_join(p_writer, nullptr); */
    
    for(int i = 0; i < loops; i++)
    {
        pthread_join(p_readers[i], nullptr);
    }

    return 0;
}

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值