OS进程同步实验报告.docx
- 文档编号:3572785
- 上传时间:2022-11-24
- 格式:DOCX
- 页数:13
- 大小:40.50KB
OS进程同步实验报告.docx
《OS进程同步实验报告.docx》由会员分享,可在线阅读,更多相关《OS进程同步实验报告.docx(13页珍藏版)》请在冰豆网上搜索。
OS进程同步实验报告
学 号
2016143222
姓名
王娟
实验日期
2018.4.20
实验名称
线程的执行与同步
实验目的:
1.在本实验中,通过对线程创建、改变线程优先级及线程的启动与停止等方法的了解,来加深对Windows2000线程控制与管理的理解。
2.在本实验中,通过对临界区,互斥体和信号量的使用,来加深对Windows2000线程同步的理解。
实验内容:
1.运行程序3-1、3-2,并对运行结果进行分析。
2.运行程序4-1、4-2、4-3、4-4,并对运行结果进行分析。
实验原理:
线程之间以并发(注意不是并行,并行是互相毫不相干的各自同时执行)方式执行。
但是由于多线程往往拥有共同的临界资源,比如打印机,全局变量,控制台等。
这种资源一次只允许一个线程访问和使用(互斥)。
使得多线程必须要按照一定的次序串行执行(同步)共享资源。
在各个线程中使用、操作共享资源的代码段叫临界区。
信号量是一种数据结构,一般由两个成员组成:
数值、指针。
一般来说,信号量的值代表着当前资源的使用情况,信号量的值只能由P、V操作来改变,不同的信号量可以实现不同场景下的线程同步,但是都是由P、V操作来实现。
P、V操作都是原子操作;P申请一个单位资源,申请不到就等待;V释放一个单位资源,如果有等待线程就唤醒
P操作:
P(S)--S
如果S<0,等待;如果S>=0,继续
V操作:
V(S)++S
如果S>0,继续;如果S<=0,唤醒
根据P、V操作中使用的变量个数和大小,可以实现多种线程同步的场景:
互斥体、信号量、读写锁等。
这些同步工具都有类似的接口,如获取(acquire)、释放(release)等。
实验过程与结果:
1.程序3_1.
#include
#include
#include
DWORDWINAPIThreada(LPVOIDlpPara)
{
cout<<"Threadaisrunning."< //④ExitThread (2); for(intn=0;n<10;n++) {cout<<"Inthreada.n="< //①Sleep(100); } return(0); } voidmain() {HANDLEha; cout<<"Mainthreadisrunning"< ha=CreateThread(NULL,//默认的安全属性,不继承该线程句柄 0,//由系统设置默认大小的堆栈 Threada,//入口函数 NULL,//函数参数指针 0,//启动选项 NULL);//返回线程ID //②SuspendThread(ha); //②cout<<"Threadaissuspended.\n"; //⑤TerminateThread(ha,0); cout<<"Threadaiscreated.\n"; for(intn=0;n<10;n++) {cout<<"Inmainthread"< //①Sleep(500); } //②ResumeThread(ha); //③WaitForSingleObject(ha,INFINITE);//用10代替INFINITE } 依次执行各标号语句,分析执行的结果。 1、sleep(500);程序停半秒,然后继续向下执行。 2、SuspendThread(ha);暂停ha线程 ResumeThread(ha);启动被挂起的ha线程,恢复线程运行 3.WaitForSingleObject线程一直被挂起,直到ha所指向的对象变为有信号状态时为止 4、ExitThread: 指定调用线程的退出代码在一个过程中,某一时刻只有一个线程可以在一个动态链接库的初始化或分离程序。 5.TerminateThread在线程外终止一个线程,用于强制终止线程 2.程序3_2 #include #include #include classCWorkerThread {public: CWorkerThread(LPCTSTRszName): m_szName(szName),m_hThread(INVALID_HANDLE_VALUE) {m_hThread=CreateThread( NULL,0,ThreadProc,reinterpret_cast } virtual~CWorkerThread(){CloseHandle(m_hThread);} virtualvoidWaitForCompletion(){WaitForSingleObject(m_hThread,INFINITE);} virtualvoidSetPriority(intnPriority){SetThreadPriority(m_hThread,nPriority);} virtualvoidSuspend(){SuspendThread(m_hThread);} virtualvoidResume(){ResumeThread(m_hThread);} protected: staticDWORDWINAPIThreadProc(LPVOIDlpParam) {CWorkerThread*pThis=reinterpret_cast pThis->DoStuff(); return(0); } virtualvoidDoStuff() {for(intn=0;n<10;++n) printf("Thread%sID: %d,count%d\n",m_szName,GetCurrentThreadId(),n); } protected: HANDLEm_hThread; LPCTSTRm_szName; }; voidmain() { CWorkerThreadwtA("A"); CWorkerThreadwtB("B"); wtA.SetPriority(THREAD_PRIORITY_LOWEST); wtB.Suspend(); wtA.WaitForCompletion(); wtB.Resume(); wtB.WaitForCompletion(); printf("Boththreadscomplete.\n"); } 阅读、调试、运行、分析程序,写出运行结果,这个结果与你期望的一致吗? (从进程并发的角度对结果进行分析) 思考题 1.分析进程与线程创建及控制的异同。 (1)调度: 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位 (2)并发性: 不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行 (3)拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源. (4)系统开销: 在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。 2.分析线程各API函数的功能及使用方法。 线程创建函数: CreateThread() 获得线程优先级函数: GetThreadPriority() 设置线程优先级函数: SetThreadPriority() 判断线程当前是否处于运行状态函数: GetExitCodeThread() 获得线程标识符函数: GetCurrentThreadID() ResumeThread() 用于挂起线程的函数: SuspendThread() 终止线程函数: ExitThread() 终止线程函数: TerminateThread() 实验过程与结果: 程序4_1 两个线程无同步代码,观察i值。 #include #include #defineM90000//循环次数要很大,可多次尝试一些值 inti=0; DWORD_stdcallfun1(LPVOIDp1) {for(intj=0;j return0; } DWORD_stdcallfun2(LPVOIDp1) {for(intj=0;j return0; } intmain() {DWORDid1,id2; HANDLEhThread[2]; hThread[0]=CreateThread(0,0,fun1,0,0,&id1); hThread[1]=CreateThread(0,0,fun2,0,0,&id2); WaitForMultipleObjects(2,//等待句柄的数量 hThread,//句柄数组的指针 1,//TRUE等待所有对象,FALSE第一个信号到来就向下执行 INFINITE);//一直等待 cout<<"i="< return0; } 观察后发现i的值不一定为0,有时是很大的正数,有时是很大的负数,这就是多个线程在操作同一变量i时,未同步时带来的严重问题。 程序4_2 给这两个线程加上临界区同步代码,再来观察对i值的影响。 #include #include inti; constintM=9000000; CRITICAL_SECTIONcs; DWORDWINAPIfun1(LPVOIDp1) {for(intj=0;j EnterCriticalSection(&cs); i++; LeaveCriticalSection(&cs); } return0; } DWORD_stdcallfun2(LPVOIDp1) {for(intj=0;j EnterCriticalSection(&cs); i--; LeaveCriticalSection(&cs); } returnj; } intmain() {DWORDid1,id2; HANDLEhThread[2]; InitializeCriticalSection(&cs); hThread[0]=CreateThread(0,0,fun1,0,0,&id1); hThread[1]=CreateThread(0,0,fun2,0,0,&id2); WaitForMultipleObjects(2,hThread,1,INFINITE); cout<<"i="< DeleteCriticalSection(&cs); return0; } 无论如何执行,i的值总是0,结果是正确的。 程序4_3 利用互斥体保护共享资源 #include #include inti=0; constintM=9000000; HANDLEhMutex; DWORDWINAPIfun1(LPVOIDp1) {for(intj=0;j WaitForSingleObject(hMutex,INFINITE); i++; ReleaseMutex(hMutex); } return0; } DWORD_stdcallfun2(LPVOIDp1) {for(intj=0;j WaitForSingleObject(hMutex,INFINITE); i--; ReleaseMutex(hMutex); } returnj; } intmain() {DWORDid1,id2; HANDLEhThread[2]; hMutex=CreateMutex( NULL,//缺省的安全属性,句柄不被子进程继承 FALSE,//只初始化,不拥有 NULL);//匿名的 hThread[0]=CreateThread(0,0,fun1,0,0,&id1); hThread[1]=CreateThread(0,0,fun2,0,0,&id2); WaitForMultipleObjects(2,hThread,1,INFINITE); cout<<"i="< ReleaseMutex(hMutex); CloseHandle(hMutex); return0; } 程序4_4: #include #include HANDLEg_hSemThreads=INVALID_HANDLE_VALUE; staticDWORDWINAPIThreadProc(LPVOIDlpParam) { LONGnPauseMs=reinterpret_cast Sleep(nPauseMs*100); if(g_hSemThreads! =INVALID_HANDLE_VALUE) { LONGnPrevCt(0); if(ReleaseSemaphore(g_hSemThreads,//要增加的信号量句柄 1,//增加的计数 &nPrevCt))//增加前的数值返回 { cout< "< } } return(0); } voidmain() {g_hSemThreads=CreateSemaphore(NULL,//是信号量的安全属性 5,//是初始化的信号量 5,//信号量的最大值 NULL);//信号量的名称 for(intnTotal=10;nTotal>0;nTotal--) { WaitForSingleObject(g_hSemThreads,INFINITE); LONGnPauseMs=nTotal*5; Sleep(100); HANDLEThread=CreateThread(NULL,0,ThreadProc,reinterpret_cast cout< CloseHandle(hThread); hThread=INVALID_HANDLE_VALUE; } CloseHandle(g_hSemThreads); g_hSemThreads=INVALID_HANDLE_VALUE; } 思考题 1.比较临界区和互斥体实现同步的实用方法,及程序执行的效率。 临界区是一种最直接的线程同步方式。 所谓临界区,就是一次只能由一个线程来执行的一段代码。 如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。 互斥非常类似于临界区,除了两个关键的区别: 首先,互斥可用于跨进程的线程同步。 其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 临界区与事件对象(比如互斥对象)的最大的区别是在性能上。 临界区在没有线程冲突时,要用10~15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。 当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。 2.分析信号量对象的实用情况和方法,分析创建信号量对象的API函数各参数的含义。 信号量对象。 它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。 CreateSemaphore()创建一个信号量 OpenSemaphore()打开一个已经创建的信号量 ReleaseSemaphore()释放对信号量的所有权 实验小结 实验4 (一) 每个实验都能认认真真完成,从实验中体会到同步机制的重要性。 能够认真学习API函数,并将自己所学的知识运用到实验中。 (二) 对于一些效率要求很高的程序,不适宜用WindowsAPI,而应该自己编写效率较高的线程同步算法,就目前而言,这还是我比较欠缺的,急需要锻炼的能力。 (三) 通过这次实验,体会到同步机制的重要性,掌握了有关线程同步的一些知识: 两个或多个线程争相执行同一段代码或访问同一资源的现象称为竞争;可能造成竞争的共享代码段或资源就被称为临界区;在任何时刻都能有一个线程在临界区中的现象被称为互斥。 实验3 通过本次实验,了解了在Windows系统中,进程是资源的拥有者,线程是系统调度的单位。 进程创建后,其主线程也随即被创建。 了解了线程的引入是为了减少程序并发执行时所付出的时空开销,使得并发粒度更细,并发更好。 这个思路下,“独立分配资源”的任务仍由进程完成,作为系统资源保护和分配的基本单位,而“被调度分派执行”的任务被交付给线程的实体完成,线程作为系统调度和分配的基本单位,会被频繁的调度和切换。 与进程相比,线程的有点为: 快速线程切换,同一个进程中的多线程切换,只需要改变堆栈和寄存器,地址空间不用变。 通信易于实现。 自动共享进程的内存和文件,线程可以自由访问全局变量,通信十分方便,而且不需要通过内核 减少管理开销。 线程创建和撤销的工作比进程少。 并发程度高。 多线程适合并行工作 备注: 批阅教师 时间 实验成绩
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- OS 进程 同步 实验 报告
![提示](https://static.bdocx.com/images/bang_tan.gif)