实验二线程与同步 nachos02.docx
《实验二线程与同步 nachos02.docx》由会员分享,可在线阅读,更多相关《实验二线程与同步 nachos02.docx(19页珍藏版)》请在冰豆网上搜索。
实验二线程与同步nachos02
实验报告
2302010220不可不戒
一、实验目的
实现Nachos的同步机制:
锁和条件变量,并利用这些同步机制实现几个基础工具类
二.实验内容&设计思想&代码
首先根据老师提供的文档,对必要的文件进行了理解后进行实验
1.实现锁机制和条件变量,并利用这些同步机制将实验一中所实现双向有序链表类修改成线程安全的
A.使用Thread:
:
Sleep实现锁机制和条件变量
在每一个函数(操作)开始时先开中断,结束后在关中断。
1.锁Lock
Acquire()
申请锁
当锁lockValue=false,说明锁不可用,当前线程进入睡眠状态。
当锁lockValue=true,说明锁可用,当前线程获得该锁,继续运行。
VoidLock:
:
Acquire()
{
//ASSERT(!
isHeldByCurrentThread());
IntStatusoldLevel=interrupt->SetLevel(IntOff);
while(lockValue)//locknotavailable
{
queue->Append((void*)currentThread);
currentThread->Sleep();
}
lockValue=true;//lockavailable
owner=currentThread;//recordthenewownerofthelock
(void)interrupt->SetLevel(oldLevel);
}
Release()
释放锁(注意:
只有拥有锁的线程才能释放锁)
lockValue=true
如果有其它线程等待该锁,将其中的一个唤醒,进入就绪态。
boolisHeldByCurrentThread()用于判定当前进程是否是锁的持有者。
voidLock:
:
Release()
{
Thread*thread;
ASSERT(isHeldByCurrentThread());
IntStatusoldLevel=interrupt->SetLevel(IntOff);
thread=(Thread*)queue->Remove();
if(thread!
=NULL)
scheduler->ReadyToRun(thread);
lockValue=false;
owner=NULL;
(void)interrupt->SetLevel(oldLevel);
}
2.条件变量Condition
Wait、Signal以及BroadCast,所有的这些操作必须在当前线程获得一个锁的前提下而且所有对一个条件变量进行的操作必须建立在同一个锁的前提下
Wait()
线程等待条件变量
释放该锁
进入睡眠状态
重新申请该锁
voidCondition:
:
Wait(Lock*conditionLock)
{
ASSERT(conditionLock->isHeldByCurrentThread());
IntStatusoldLevel=interrupt->SetLevel(IntOff);
conditionLock->Release();
queue->Append((void*)currentThread);
currentThread->Sleep();
conditionLock->Acquire();
(void)interrupt->SetLevel(oldLevel);
}
Signal()
唤醒一个等待该条件变量的线程(如果存在的话)
voidCondition:
:
Signal(Lock*conditionLock)
{
Thread*thread;
ASSERT(conditionLock->isHeldByCurrentThread());
IntStatusoldLevel=interrupt->SetLevel(IntOff);
thread=(Thread*)queue->Remove();
if(thread!
=NULL)
scheduler->ReadyToRun(thread);
(void)interrupt->SetLevel(oldLevel);
//makethreadready,comsumingtheVimmediately
}
BroadCast()
唤醒所有等待该条件变量的线程(如果存在的话)
voidCondition:
:
Broadcast(Lock*conditionLock)
{
Thread*thread;
ASSERT(conditionLock->isHeldByCurrentThread());
IntStatusoldLevel=interrupt->SetLevel(IntOff);
thread=(Thread*)queue->Remove();
while(thread!
=NULL)
{
scheduler->ReadyToRun(thread);
thread=(Thread*)queue->Remove();
}
(void)interrupt->SetLevel(oldLevel);
}
B.使用Semaphore实现锁机制和条件变量
1.不需要自行考虑关中断和阻塞队列维护等问题
2.Semaphore与条件变量的区别:
A.如果在调用Semaphore:
:
P()前调用Semaphore:
:
V()
则V操作的效果将积累下来
B.而如果在调用Condition:
:
Wait()前调用Condition:
:
Signal()
则Signal操作的效果将不积累
3.锁机制直接调用P和V操作,条件变量类似使用Thread:
:
Sleep实现
4.在头文件中加入了指向Semaphore变量的指针,用于实现Semaphore实现锁机制和条件变量。
voidLock:
:
Acquire()
{
sem_lock->P();
owner=currentThread;
}
voidLock:
:
Release()
{
ASSERT(isHeldByCurrentThread());
owner=NULL;
sem_lock->V();
}
voidCondition:
:
Wait(Lock*conditionLock)
{
ASSERT(conditionLock->isHeldByCurrentThread());
conditionLock->Release();
wait_count_thread++;
sem_condition->P();
conditionLock->Acquire();
}
voidCondition:
:
Signal(Lock*conditionLock)
{
ASSERT(conditionLock->isHeldByCurrentThread());
if(wait_count_thread>0)
{
sem_condition->V();
wait_count_thread--;
}
if(wait_count_thread<0)
wait_count_thread=0;
}
//wakeupallthesleepthreads
voidCondition:
:
Broadcast(Lock*conditionLock)
{
ASSERT(conditionLock->isHeldByCurrentThread());
while(wait_count_thread--)
sem_condition->V();
if(wait_count_thread<0)
wait_count_thread=0;
}
C.用锁机制和条件变量修改双向有序链表
1.基本同实验一。
2.分别对void*DLList:
:
Remove(int*keyPtr)和voidDLList:
:
SortedInsert(void*item,intsortKey)这两个函数做了加锁的处理。
Lock&Condition实现方法:
sleep+中断禁止与启用
...............
...............
void*DLList:
:
Remove(int*keyPtr)
{
if(flag)
sleep_lock->Acquire();
......
if(flag)
while(IsEmpty())//dllistisempty
sleep_condition->Wait(sleep_lock);//wait
elseif(IsEmpty())
returnNULL;
........
if(err_type==4)
{
//printf("Removeerror\n");
currentThread->Yield();
}
........
if(flag)
sleep_lock->Release();}
..............................
2.实现一个线程安全的表结构
A.在头文件synch.h中加入boollockValue标记锁是否可用;Thread*owner用于判定锁是否为当前线程所有;
B.在初始化table时要创建新的lock锁变量,在析构函数中将其删除即可
C.要实现线程安全关键是Alloc函数与get函数,在执行时要加入锁机制,以免被线程中断二导致错误。
Alloc要求往Table中加入Object,如果满了则返回-1
intTable:
:
Alloc(char*obj)
{
if(flag==1)
tableLock->Acquire();
for(inti=0;i{
if(table[i]==NULL)
{
//currentThread->Yield();
table[i]=obj;
if(flag==1)
tableLock->Release();
returni;
}
}
if(flag==1)
tableLock->Release();
return-1;
}
Release()函数是从Table中删除table[index]
voidTable:
:
Release(intindex,intwhich,inti)
{
if(flag==1)
tableLock->Acquire();
if(index>=0&&index{
if(*table[index]!
=NULL)
{
printf("table[%d]=%cisReleasedbythread%di=%d\n",index,*table[index],which,i);
table[index]=NULL;//删除后,地址赋值NULL
}
else
printf("table[%d]isNULLbythread%di=%d\n",index,which,i);
}
else
printf("table[%d]isNULLbythread%di=%d\n",index,which,i);
if(flag==1)
tableLock->Release();
}
3.实现一个大小受限的缓冲区
A.主要设计思想参考课本5.3.2节——生产者/消费者问题。
B.三个不同信号量的初始化
n=newSemaphore("used",1);//缓冲区中的项数
s=newSemaphore("sem",0);//用于实施互斥
e=newSemaphore("empty",maxsize);//空闲空间的数目
C.Buffer实现方法semaphone&使用Semaphore实现锁机制和条件变量
注:
semaphone见代码注释
1.Read()从缓存区读取
voidBoundedBuffer:
:
Read(void*data,intsize)
{
int*r=(int*)data;
while(size--)
{
if(flag==1)
{//若readout==writein:
缓冲区没有可读数据
sem_Lock->Acquire();
while(writein==readout)
{
//printf("waitr%d\n",size);
bufferEmpty->Wait(sem_Lock);
//n->P();
}
//s->P();
}
*r++=Buffer[readout];
readout=(readout+1)%Buffersize;
if(flag==1)
{
bufferFull->Broadcast(sem_Lock);//bufferisnotfull,wakeupwaitwritingthreads
sem_Lock->Release();
//s->V();
//e->V();
}
}
}
2.Write()函数向缓存区写入数据
voidBoundedBuffer:
:
Write(void*data,intsize)
{
int*w=(int*)data;
while(size--)
{
if(flag==1)
{//若(writein+1)%Buffersize==readout:
缓冲区满。
注意:
这里有一个空间未被使用
sem_Lock->Acquire();
while((writein+1)%Buffersize==readout)
{
//printf("waitw%d\n",size);
bufferFull->Wait(sem_Lock);
//e->P();
}
//s->P();
}
Buffer[writein]=*w++;
writein=(writein+1)%Buffersize;
if(flag==1)
{
bufferEmpty->Broadcast(sem_Lock);//bufferisnotempty,wakeupwaitwritingthreads
sem_Lock->Release();
//s->V();
//n->V();
}
}
}//每次写入1bytes,就有可能线程切换.读取就会乱序
4.关于测试
代码见threadtest.cc
二、实验结果
相关参数说明:
-rscausesYieldtooccuratrandom(butrepeatable)spots
修改system.ccvoidInitalize(intargc,char**argv)bool=randomYield=TRUE
1.dllist.cc测试结果Lock&Condition实现方法:
sleep+中断禁止与启用
./nachos-q2-t2-n5-e0-rs2flag=1使用锁机制e=0没有错误
./nachos-q2-t2-n5-e0-rs2flag=0不使用锁机制e=0没有错误
./nachos-q2-t2-n5-e0-rs2flag=1使用锁机制e=4有错误
./nachos-q2-t2-n5-e0-rs2flag=0不用锁机制e=4有错误
Dllist.cc测试使用Semaphore实现锁机制和条件变量
./nachos-q2-t2-n5-e4flag=0不使用锁
./nachos-q2-t2-n5-e4flag=1使用锁
2.Table测试结果
./nachos-q3-t2-rs2flag=1使用锁机制结果没问题
./nachos-q3-t2-rs2falg=0不使用锁结果出现错误
3.BoundedBuffer测试结果实现方法semaphone
./nachos-q4-t5-rs3flag=1使用信号量
./nachos-q4-t5-rs3flag=0不使用信号量
让每一个线程往缓存区写入当前线程号:
实现方法lock&condition
三、实验中遇到的问题&总结
问题:
1.开始线程不能随机切换,(-rs在助教的帮助下解决)
解决:
删除nachos-3.4,重新编译。
2.在链表加锁时,一开始忘记对链表为空的情况进行判定,出现了错误
解决:
在链表中对有可能因为链表为空的情况加入判断,如果出现空则调用Wait等待。
在插入线程执行完以后待用Signal唤醒的等待的进程。
3.在对Table进行操作时,定义了多线程共享table_arr[]数组,结果一直出错,原来
多线程共享全局变量的值会被修改,解决:
定义局部变量table_arr[]数组。
4.在Broadcast唤醒等待的线程,while()循环后wait_count_thread可能小于0,
当小于0时没赋值0,出现睡死情况。
解决:
while()结束判断wait_count_thread是否小于0,小于0赋值为0.
5.总结:
通过本次实验让我们对信号量的工作机制有了更加深入的理解,可以从线程的观点来考察不同信号量对线程的影响,了解了操作系统对线程并发性的处理过程,对操作系统的架构有了了解。
实验分工: