这篇博客对应线上课程的第六次实验。
网易云课堂:http://mooc.study.163.com/learn/USTC-1000002006?tid=1000081002#/learn/announce
实验楼:https://www.shiyanlou.com/courses/122
代码库:http://git.shiyanlou.com/chenxu/shiyanlou_cs122
实验目的
要写一个线程安全的链表,为LinkTable添加互斥锁,完成“读写互斥”、“单写多读”的目的。
实验思路
互斥锁原理
首先要了解一下互斥锁的原理。在线程实际运行过程中,需要对多个线程操作的“共享空间”保持同步,这时可以用互斥锁来完成任务。
想进一步了解互斥锁的可以看看这篇博客:
http://blog.youkuaiyun.com/harry_lyc/article/details/6222920
读者写者问题
读者一写者问题是一个用信号量实现的经典进程同步问题。在系统中,一个数据集( 如文件或记录) 被几个并发进程共享,这些线程分两类,一部分只要求进行复操作,称之为“读者”;另一类要求写或修改操作,我们称之为“写者“。
读者优先的算法是一种解决办法:当没有写进程正在访问共享数据集时,读进程可以进入访问,否则必须等待。而读者优先的算法存在“饿死写者”线程的问题:只要有读者不断到来,写者就要持久地等待,直到所有的读者都读完且没有新的读者到来时写者才能写数据集。
在写者优先算法中,我们要实现的目标是:
1. 要让读者与写者之间、以及写者与写者之问要互斥地访同数据集;
2. 在无写进程到来时各读者可同时访问数据集;
3. 在读者和写者都等待时访问时写者优先.
想进一步了解读者写者问题的可以看看这几篇博客:
http://blog.youkuaiyun.com/cz_hyf/article/details/4443551
实验过程
实现部分借鉴了这位博主的内容 http://blog.youkuaiyun.com/yaozhiyi/article/details/7563869
互斥量
在Linktable内添加一系列互斥锁:
struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
int readerCnt;
int writerCnt;
pthread_mutex_t accessReaderCnt;
pthread_mutex_t accessWriterCnt;
pthread_mutex_t writeLock;
pthread_mutex_t readerLock;
pthread_mutex_t outerLock;
};
- readerCnt:统计读者的数量,用于并发读取;
- writerCnt:统计写者的数量,用于控制
- accessReaderCnt:互斥各线程对readerCnt的访问;
- accessWriterCnt:互斥各线程对writerCnt的访问;
- writeLock:限制只有一个写者修改数据;
- readerLock:写者优先,用于限制所有读者的锁;
- outerLock:在一个读者到来时,阻塞后续读者,使写者有获取readerLock的优先性。
CreateLinkTable和DeleteLinkTable两个函数的修改很简单,只是加入了初始化互斥量和销毁互斥量的操作。主要的操作还是在AddLinkTableNode、DelLinkTableNode、SearchLinkTableNode和GetNextLinkTableNode这4个函数中,其中前两个为写操作,后两个为读操作。
写操作
在这里,使用AddLinkTableNode来进行说明读写互斥与写者优先的实现。
首先,在获得写操作互斥锁之前,先获取accessWriterCnt锁,来控制写者并发时不发生冲突;之后对写者数加1,且当第一个写者到来时对readerLock进行加锁,阻塞之后到来的所有读者。
pthread_mutex_lock(&(pLinkTable->accessWriterCnt));
{//临界区,希望修改 writerCnt,独占 writerCnt
pLinkTable->writerCnt++;
if(pLinkTable->writerCnt == 1){
//阻止后续的读者加入待读队列
pthread_mutex_lock(&(pLinkTable->readerLock));
}
}
pthread_mutex_unlock(&(pLinkTable->accessWriterCnt));
在释放accessWriterCnt锁之后,本写者进行写操作,其他写者可以进入队列等待writeLock以获得写权限。
pthread_mutex_lock(&(pLinkTable->writeLock));
{//临界区,限制只有一个写者修改数据
//write();
if (pLinkTable->pHead == NULL)
{
pLinkTable->pHead = pNode;
}
if (pLinkTable->pTail == NULL)
{
pLinkTable->pTail = pNode;
}
else
{
pLinkTable->pTail->pNext = pNode;
pLinkTable->pTail = pNode;
}
pLinkTable->SumOfNode += 1;
}
pthread_mutex_unlock(&(pLinkTable->writeLock));
最后,判断一下写者队列,队列为空时再释放readerLock。
pthread_mutex_lock(&(pLinkTable->accessWriterCnt));
{//临界区,希望修改 pLinkTable->writerCnt,独占 pLinkTable->writerCnt
pLinkTable->writerCnt--;
if(pLinkTable->writerCnt == 0){
//阻止后续的读者加入待读队列
pthread_mutex_unlock(&(pLinkTable->readerLock));
}
}
pthread_mutex_unlock(&(pLinkTable->accessWriterCnt));
读操作
使用SearchLinkTableNode来说明读操作的实现。
首先对outerLock进行加锁,后续读者等待,在释放outerLock之前先释放readerLock,这时写者可获取readerLock,而其他读者需要等待outerLock;第一个到来的读者对writeLock进行加锁,防止在读取时写者进行更改。
pthread_mutex_lock(&(pLinkTable->outerLock)); //假如写者锁定了readerLock,那么成千上万的读者被锁在这里
{//临界区
pthread_mutex_lock(&(pLinkTable->readerLock));//只被一个读者占有
{//临界区
pthread_mutex_lock(&(pLinkTable->accessReaderCnt));//代码段 1
{//临界区
pLinkTable->readerCnt++;
if(pLinkTable->readerCnt == 1){
pthread_mutex_lock(&(pLinkTable->writeLock));
}
}
pthread_mutex_unlock(&(pLinkTable->accessReaderCnt));
}
pthread_mutex_unlock(&(pLinkTable->readerLock));//释放时,写者将优先获得readerLock
}
pthread_mutex_unlock(&(pLinkTable->outerLock));
查找成功的情况下(读取完成),判断读者队列,队列为空时释放writeLock。
tLinkTableNode * pNode = pLinkTable->pHead;
while (pNode != NULL)
{
if (Conditon(pNode, args) == SUCCESS)
{
pthread_mutex_lock(&(pLinkTable->accessReaderCnt));//代码段2
{//临界区
pLinkTable->readerCnt--;
if(pLinkTable->readerCnt == 0){
pthread_mutex_unlock(&(pLinkTable->writeLock));//在最后一个并发读者读完这里开始禁止写者执行写操作
}
}
pthread_mutex_unlock(&(pLinkTable->accessReaderCnt));
return pNode;
}
pNode = pNode->pNext;
}