传送门:
进程同步与互斥——哲学家就餐问题源码实现(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;
}