1、理解线程并发和同步的概念
2、学会实现两个常用的线程同步的机制---锁和条件变量
3、学会使用锁和条件变量来确保共享变量是线程安全的
本次实验的目的在于将nachos中的锁机制和条件变量的实现补充完整,并利用这些同步机制实现几个基础工具类。
实验内容分三部分:
实现锁机制和条件变量,并利用这些同步机制将实验一中所实现双向有序链表类修改成线程安全的;
实现一个线程安全的表结构;
实现一个大小受限的缓冲区
dllist类:
#include<cstdlib> #include"dllist.h" #include<iostream> #include"system.h" #include<cstdio> #include<ctime> element:: element(int this_key) { key=this_key; prev=NULL; next=NULL; } element:: element() { prev=NULL; next=NULL; } element:: ~element() { } dllist:: dllist() { lock=new Lock("lock"); listempty=new Condition("listempty"); head=new element(-1); end=new element(101); head->prev=NULL; head->next=end; end->prev=head; end->next=NULL; } dllist::~dllist() { } void dllist::sortedinsert(int this_key) {//申请一个内存并插入链表中间 if(flag==4||flag==6||flag==7) lock->Acquire(); printf("Thread%d:\t Insert:%d\t\n",(char *)currentThread,this_key); element *insert=new element(this_key); element *temp=head; element *temp_1=NULL; printf("in insert node is %d\n",insert->key); while(temp->key<this_key && temp->next!=NULL) {//interrupt 5 to null interrupt 4 to null temp=temp->next; } //printf("%d\n",temp->key); if(temp->key>=this_key) { //printf("error\n"); //if(temp->next->key!=101) temp=temp->prev; temp_1=temp->next; temp->next=insert; insert->prev=temp; temp_1->prev=NULL; insert->next=NULL; if(flag==4) { printf("four-interrupt\n\n"); currentThread->Yield(); } if(temp->next!=NULL) { temp_1->prev=insert; insert->next=temp_1; } } else { //printf("flag=4!\n,"); temp->next=insert; insert->prev=temp; insert->next=NULL; if(flag==4) { printf("four-interrupt\n\n"); currentThread->Yield(); } } //插入正确位置 //printf("being in insert node is%d %d %d\n",head->key,head->next->key,temp->next->key); /*insert->next=head->next; insert->prev=head; head->next->prev=insert; head->next=insert;*/ temp=head; while(temp->next!=NULL) { printf("%d\n",temp->key); temp=temp->next; } printf("%d\n",temp->key); // if(flag==4) //{ listempty->Signal(lock); lock->Release();//} } void dllist::printfN() { //element *temp=head; // while(temp->key<101) /*while(temp->next!=NULL) { printf("%d\n",temp->key); temp=temp->next; } printf("%d\n",temp->key); */ //printf("queue is%d, %d",temp->key,temp->next->key); } void dllist::sortedremove() {//删除链表中的第一个数据直到没有数据了。int flag=0; if(flag==5||flag==6||flag==7) lock->Acquire(); // printf("Thread%d:\t delect:",thread_num); element *temp=NULL; element *temp_next=NULL; // printf("aa\n"); if(head->next==NULL|| (head->next!=NULL&&head->next->key==101)) { // if(flag==5||flag==6||flag==7) listempty->Wait(lock); } if(head->next!=NULL&&head->next->key!=101) // if(head->next!=NULL) { // printf("bb\n"); if( head->next->next!=NULL) { temp=head->next; temp_next=temp->next; printf("Thread%d:\t delect:",(char *)currentThread); printf("%d\n",temp->key); // printf("gg\n"); free(temp); // printf("hh\n"); if(flag!=6&&flag!=7) head->next=NULL; if(flag==5||flag==6||flag==7) { printf("five-interrupt\n"); currentThread->Yield(); } head->next=temp_next; temp_next->prev=head; } else{ temp=head->next; printf("Thread%d:\t delect:",(char*)currentThread); printf("%d\n",temp->key); // printf("ii\n"); free(temp); // printf("jj\n"); if(flag==5||flag==6) { printf("five-interrupt\n"); currentThread->Yield(); } // printf("jj\n"); head->next=NULL; } } else if(head->next==NULL) printf("\n链表已经没有数据\n"); // printf("ee\n":); temp=head; // if(flag==5||flag==6||flag==7) lock->Release(); // printf("a"); /* while(temp->next!=NULL) { printf("%d\n",temp->key); temp=temp->next; } printf("%d\n",temp->key); printf("b");*/ } void dllist::remove(int key) {//删除链表中的数据与指定的值相等 element *temp=head->next; element *temp_next=temp->next; element *temp_next_next=NULL; if(head->next->key!=101&&head->next!=NULL) { while(temp->key<key) temp_next=temp->next; if(temp_next->key!=key) printf("链表中没有该数据"); else {//存储该对应数据的前后数据指针 temp=temp_next->prev; temp_next_next=temp_next->next; //修改前后指针 temp->next=temp_next_next; temp_next_next->prev=temp; } free(temp_next); } else printf("链表已经没有数据"); }
#include"synch.h" class element { public: int key; element *prev; element *next; element(int this_key); element(); ~element(); }; class dllist { public: Lock *lock; Condition *listempty; public: dllist(); ~dllist(); dllist(int key); element *head; element *end; void sortedinsert(int key); void sortedremove(); void remove(int this_key); void printfN(); }; extern void insertN(int thread_num,dllist *dllist_one,int insert_num); extern void delectN(int thread_num,dllist *dllist_one,int insert_num); extern int testnum; extern int flag; //extern int thread_num;
dllist-driver.cc
#include<cstdlib> #include"dllist.h" #include"system.h" #include<cstdio> #include<ctime> Lock *lock1=new Lock("lock"); Condition *listempty1=new Condition("listempty"); extern int flag; void insertN(int thread_num,dllist *dllist_one,int insert_num) {int key=0; if(flag==2||flag==6||flag==3) lock1->Acquire(); //dllist_one=new dllist(); for(int i=0;i<insert_num;i++) { key=rand()%100; // printf("Thread%d:\t Insert:%d\t\n",thread_num,key); dllist_one->sortedinsert(key); if(flag==2||flag==6) { printf("second interrupt\n"); currentThread->Yield(); } } listempty1->Signal(lock1); lock1->Release(); } void delectN(int thread_num,dllist *dllist_one,int insert_num) { if(flag==3) lock1->Acquire(); for(int i=0;i<insert_num;i++) { // printf("Thread%d:\t delect:",thread_num); dllist_one->sortedremove(); if(flag==3) { printf("third interrupt\n"); currentThread->Yield(); } } lock1->Release(); }
threadtest.cc
// threadtest.cc // Simple test case for the threads assignment. // // Create two threads, and have them context switch // back and forth between themselves by calling Thread::Yield, // to illustratethe inner workings of the thread system. // // Copyright (c) 1992-1993 The Regents of the University of California. // All rights reserved. See copyright.h for copyright notice and limitation // of liability and disclaimer of warranty provisions. #include "copyright.h" #include "system.h" #include "dllist.h" extern void Insertlist(int n,DLList *list,int which); extern void Insertlist_new(int n,DLList *list,int which); extern void Removelist(int n,DLList *list,int which); extern void Removelist_new(int n,DLList *list,int which); char choose; // testnum is set in main.cc int testnum = 1; DLList *list; //extern DLList list; //---------------------------------------------------------------------- // SimpleThread // Loop 5 times, yielding the CPU to another ready thread // each iteration. // // "which" is simply a number identifying the thread, for debugging // purposes. //---------------------------------------------------------------------- void SimpleThread(int which) { switch(choose) { case '1': Insertlist(3,list,which); Removelist(3,list,which); break; case '2': Insertlist_new(3,list,which); Removelist_new(3,list,which); break; } // printf("*** thread %d \n", which); currentThread->Yield(); } //---------------------------------------------------------------------- // ThreadTest1 // Set up a ping-pong between two threads, by forking a thread // to call SimpleThread, and then calling SimpleThread ourselves. //---------------------------------------------------------------------- void ThreadTest1() { DEBUG('t', "Entering ThreadTest1"); Thread *t = new Thread("forked thread"); t->Fork(SimpleThread, 1); SimpleThread(0); } //---------------------------------------------------------------------- // ThreadTest // Invoke a test routine. //---------------------------------------------------------------------- void ThreadTest() { while(1) { printf("1.未加锁的演示结果\n2.加锁后的演示结果\n"); choose = getchar(); if(choose != '1' && choose !='2') printf("error\n"); else break; } switch (testnum) { case 1: list=new DLList(); ThreadTest1(); break; default: printf("No test specified.\n"); break; } }
自己用sleep实现的锁预计条件变量
// synch.cc // Routines for synchronizing threads. Three kinds of // synchronization routines are defined here: semaphores, locks // and condition variables (the implementation of the last two // are left to the reader). // // Any implementation of a synchronization routine needs some // primitive atomic operation. We assume Nachos is running on // a uniprocessor, and thus atomicity can be provided by // turning off interrupts. While interrupts are disabled, no // context switch can occur, and thus the current thread is guaranteed // to hold the CPU throughout, until interrupts are reenabled. // // Because some of these routines might be called with interrupts // already disabled (Semaphore::V for one), instead of turning // on interrupts at the end of the atomic operation, we always simply // re-set the interrupt state back to its original value (whether // that be disabled or enabled). // // Copyright (c) 1992-1993 The Regents of the University of California. // All rights reserved. See copyright.h for copyright notice and limitation // of liability and disclaimer of warranty provisions. #include "copyright.h" #include "synch-sleep.h" #include "system.h" //---------------------------------------------------------------------- // Semaphore::Semaphore // Initialize a semaphore, so that it can be used for synchronization. // // "debugName" is an arbitrary name, useful for debugging. // "initialValue" is the initial value of the semaphore. //---------------------------------------------------------------------- Semaphore::Semaphore(char* debugName, int initialValue) { name = debugName; value = initialValue; queue = new List; } //---------------------------------------------------------------------- // Semaphore::Semaphore // De-allocate semaphore, when no longer needed. Assume no one // is still waiting on the semaphore! //---------------------------------------------------------------------- Semaphore::~Semaphore() { delete queue; } //---------------------------------------------------------------------- // Semaphore::P // Wait until semaphore value > 0, then decrement. Checking the // value and decrementing must be done atomically, so we // need to disable interrupts before checking the value. // // Note that Thread::Sleep assumes that interrupts are disabled // when it is called. //---------------------------------------------------------------------- void Semaphore::P() { IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while (value == 0) { // semaphore not available queue->Append((void *)currentThread); // so go to sleep currentThread->Sleep(); } value--; // semaphore available, // consume its value (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //---------------------------------------------------------------------- // Semaphore::V // Increment semaphore value, waking up a waiter if necessary. // As with P(), this operation must be atomic, so we need to disable // interrupts. Scheduler::ReadyToRun() assumes that threads // are disabled when it is called. //---------------------------------------------------------------------- void Semaphore::V() { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); thread = (Thread *)queue->Remove(); if (thread != NULL) // make thread ready, consuming the V immediately scheduler->ReadyToRun(thread); value++; (void) interrupt->SetLevel(oldLevel); } // Dummy functions -- so we can compile our later assignments // Note -- without a correct implementation of Condition::Wait(), // the test case in the network assignment won't work! //构造函数,初始化变量 Lock::Lock(char* debugName) { name = debugName; lock_flag = FREE; waitlock_queue = new List(); haslock_thread = NULL; } //析构函数 Lock::~Lock() { delete waitlock_queue; } //当前线程获得锁的函数 void Lock::Acquire() { IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while(lock_flag == BUSY) //wait until the lock is FREE { waitlock_queue->Append( (void *)currentThread );//将当前线程插入到等待队列中 currentThread->Sleep(); //当前线程进入睡眠 } lock_flag = BUSY; //then set it to BUSY haslock_thread = currentThread;//当前线程拥有锁 (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //释放锁 void Lock::Release() //把线程 从堵塞在锁队列 里面的换到 就绪队列里面 { Thread *ready_thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts ASSERT(isHeldByCurrentThread() );//检查当前线程是否拥有锁 lock_flag = FREE; //set lock to be FREE haslock_thread = NULL; ready_thread = (Thread *)waitlock_queue->Remove(); //waking up a thread waiting in Acquire if(ready_thread != NULL) scheduler->ReadyToRun(ready_thread); (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //检查当前线程是否拥有锁 bool Lock::isHeldByCurrentThread() { if( lock_flag == BUSY && haslock_thread == currentThread ) return true; else return false; } //构造函数,初始化变量 Condition::Condition(char* debugName) { name = debugName; waitcondition_queue = new List(); } Condition::~Condition() { delete waitcondition_queue; } //线程等待条件变量 void Condition::Wait(Lock* conditionLock) //传锁主要是为了判断当前锁是否有锁住东西与锁里面的队列无关 { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts 保存以前的中断向量 //因为中断是以随机方式发生, 从而有可能抢占刚刚获得lock的中断处理程序。 //如果抢占的中断程序也刚好需要这把锁, 或者说是其后抢占的内核程序需要这把锁, 而这把锁没有释放, 从而死锁 //如果系统中存在死锁,对系统的影响将是显而易见的。这预示着操作系统的宝贵资源被无法完成的任务占据。 //所以检测和预防死锁是非常必要的。但是由于操作系统的不确定性,死锁的检测和预防将是非常复杂的工作 thread = currentThread; ASSERT(conditionLock->isHeldByCurrentThread() );//所有的这些操作必须在当前线程获得一个锁的前提下 也就是当前线程里面的锁变量 已经获得锁。 conditionLock->Release(); //release the lock 释放锁 waitcondition_queue->Append((void *)thread);//当前线程被放进条件变量的队列 thread->Sleep(); //进入睡眠状态,relinquish the CPU conditionLock->Acquire(); //re-acquire the lock 当当前线程再次获得锁时 一般是表示 已经有生产了。 但如何确定是消费者运行后还是生产者运行后再次获得呢 //因为只有signal才能再次把该线程从条件变量里面的锁解出来 当获得锁时也表示当前线程已经不在锁的等待队列里面,只可能被唤到条件队列或者 直接执行完 (void) interrupt->SetLevel(oldLevel); // re-enable interrupts 恢复中断 } //唤醒一个等待该条件变量的线程 void Condition::Signal(Lock* conditionLock) { Thread* thread_ready; IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts thread_ready = (Thread *)waitcondition_queue->Remove(); //移走条件变量里面的线程 ASSERT(conditionLock->isHeldByCurrentThread() ); if(thread_ready != NULL) scheduler->ReadyToRun(thread_ready); //将一个线程从 阻塞队列里面 换到就绪队列里面 (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //唤醒所有等待该条件的线程 void Condition::Broadcast(Lock* conditionLock) { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while( (thread = (Thread *)waitcondition_queue->Remove() ) != NULL) scheduler->ReadyToRun(thread); (void) interrupt->SetLevel(oldLevel); // re-enable interrupts }
线程安全的 Table类
#include "Table.h" Table::Table(int size) // create a table to hold at most 'size' entries. { S = size; lock = new Lock("Tablelock"); L = new int[size]; value = new int[size]; for ( int i=0; i<size; i++ ) value[i] = 0; //初始化都为0 } Table::~Table() //析构函数 { for ( int i=0; i<=S; i++ ){ Release(i); delete lock; delete value; } int Table::Alloc(void *object) // allocate a table slot for 'object'. // return the table index for the slot or -1 on error. { int i; lock->Acquire(); for ( i=0; i<S; i++ ) if ( value[i] == 0 ) { value[i] = 1; object = (void *)(L+i);//应该反过来才对吧! break; } lock->Release(); if ( i<S ) return i+1;//具体的位置应该是数组下标加1 else return -1; } void *Table::Get(int *index) // return the object from table index 'index' or NULL on error. // (assert index is in range). Leave the table entry allocated // and the pointer in place. { if ( index>0 && index<=S ) return (void *)(L+index-1); else return NULL; } void Table::Release(int index) // free a table slot { lokc->Acquire(); if ( index>0 && index<=S ) delete (L+index-1); //同时应该把该值 对应的value 数组进行修改吧! lock->Release(); }