好文档 - 专业文书写作范文服务资料分享网站

网络编程技术实验2——Windows线程同步和互斥

天下 分享 时间: 加入收藏 我要投稿 点赞

实验2 Windows线程同步和互斥

实验目的

1、了解Windows内核对线程同步的支持。

2、了解C的线程函数库及Windows 基本的线程API 函数的使用。 3、进一步理解线程的同步控制原理。

预备知识

一、Windows线程同步机制(注:互斥是同步的一种特例)

? 事件(Event)

? 临界区(Critical Section) ? 互斥量(Mutex) ? 信号量(Semaphore) 1、是否能跨进程使用?

互斥量、信号量、事件都可以跨进程来实现同步数据操作。

临界区只能用在同一进程的线程间互斥,因为临界区无名(无句柄)。如果只为了在进程内部用的话,使用临界区会带来速度上的优势并能够减少资源占用量。 2、其它区别

临界区:访问临界资源的代码段。课堂上讲过。(存钱、取钱的例子还记得吗?) 互斥量:资源独占使用 信号量:资源计数器

事件对象:可以通过“通知”的方式来保持线程的同步。事件是WIN32中最灵活的线程间同步机制。事件存在两种状态:激发状态(Signaled or True)未激发状态(Unsignaled or False)。 3、详细解释:

(见下面实验内容每个程序前)

二、VC++(略)

实验内容

1、用事件(Event)对象来进行线程同步

? 事件可分为两类:

? 手动设置: 这种对象只可能用程序手动设置,在需要该事件或者事件发生

时,采用SetEvent及ResetEvent来进行设置。 ? 自动恢复: 一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再

次设置。

? _beginthread函数:创建一个线程。所在库文件:#include uintptr_t _beginthread(

void( *start_address )( void * ), unsigned stack_size, void *arglist );

返回值:

假如成功,函数将返回一个处理信息对这个新创建的线程。如果失败_beginthread将返回-1。 start_address

新线程的起始地址 ,指向新线程调用的函数的起始地址stack_size stack_size 新线程的堆栈大小,可以为0arglist arglist 传递给线程的参数列表,无参数是为NULL

? CreateEvent函数:创建事件对象 windows.h HANDLE CreateEvent( // SECURITY_ATTRIBUTES结构指针,可为NULL

LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, // 手动/自动 // TRUE:在WaitForSingleObject后必须手动调用ResetEvent清除信号 // FALSE:在WaitForSingleObject后,系统自动清除事件信号 BOOL bInitialState, //初始状态 LPCTSTR lpName //事件的名称 );

? 使用“事件”机制应注意以下事项:

? 如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要

与系统命名空间中的其它全局命名对象冲突; ? 事件是否要自动恢复; ? 事件的初始状态设置。

? 由于event对象属于内核对象,故进程B可以调用OpenEvent函数通过对象的名字

获得进程A中event对象的句柄,然后将这个句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函数中。此法可以实现一个进程的线程控制另一进程中线程的运行,例如:

HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,\ ResetEvent(hEvent);

验证程序:3个线程。主线程创建2个线程。一读,一写。写线程(并不真写,只是输出writing等字符串)完成后,读线程才能读,读线程完成后,主线程才能结束。 新建一个Win32控制台应用程序项目(win32 console application) #include \#include #include #include #include

HANDLE evRead,evFinish;//全局变量,事件对象的句柄 void ReadThread(LPVOID param) {

WaitForSingleObject(evRead, INFINITE);//等待evRead被激活 cout<<\读完成,唤醒主线程\ SetEvent(evFinish);//激活evFinish事件 }

void WriteThread(LPVOID param) {

cout<<\写完成,唤醒读线程\ SetEvent(evRead);//激活evRead事件 }

int main(int argc, char* argv[]) { evRead= CreateEvent(NULL,FALSE,FALSE,NULL); evFinish= CreateEvent(NULL,FALSE,FALSE,NULL); _beginthread(ReadThread,0,NULL); _beginthread(WriteThread,0,NULL); WaitForSingleObject(evFinish, INFINITE);//等待evFinish被激活 cout<<\ return 0; }

如果引入了,还有如下错误

error C2065: '_beginthread' : undeclared identifier Error executing cl.exe. 解决:

工程?设置?c/c++标签?分类(Category)下拉列表里选择代码生成(Code Generation) ?选用运行时库(Use Run-Time Library)下拉列表里选择多线程Multithreaded。然后重新编译。若还不行,再选Multithreaded DLL。

验证:用//将两条WaitForSingleObject语句屏蔽。重新编译运行,多运行几次,看结果有何不同。思考原因。

2、用临界区(Critical Section)来进行线程互斥

? 临界区是保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许

一个线程对共享资源进行访问。

? 如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问

此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 ? 临界区包含两个操作原语:

? EnterCriticalSection() 进入临界区 ? LeaveCriticalSection() 离开临界区

? EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必

须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则,临界区保护的共享资源将永远不会被释放。

? 虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多

个进程中的线程。

? 创建临界区

为了创建临界区,首先必须在进程中分配一个全局CRITICAL_SECTION数据结构:CRITICAL_SECTION gCriticalSection; ? 使用临界区

使用临界区之前,必须调用InitializeCriticalSection函数初始化:

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

? 进入临界区

调用EnterCriticalSection函数进入临界区:

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ? 离开临界区

调用LeaveCriticalSection函数退出了临界区:

VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ? 删除临界区 调用DeleteCriticalSection函数删除临界区:

VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ? 临界区一般用法:

EnterCriticalSection(& gCriticalSection ); //do something

LeaveCriticalSection(& gCriticalSection); ? 关于临界区的使用,有下列注意点:

? 每个共享资源使用一个CRITICAL_SECTION变量;

? 不要长时间运行关键代码段,当一个关键代码段长时间运行时,其他线程就

会进入等待状态,这会降低应用程序的运行性能;

? 如果需要同时访问多个资源,则可能连续调用EnterCriticalSection; ? Critical Section不是OS核心对象,如果进入临界区的线程\挂\了,将无法释

放临界资源。这个缺点在Mutex中得到了弥补。

验证程序:一个银行系统中两个线程对同一账户执行取款操作,余额1000元。一个使用ATM机取900元,另一个使用存折在柜台取700元。如果不加于控制,会使得账户余额为负数。 #include \#include #include #include #include int total =1000; HANDLE evFin[2];

CRITICAL_SECTION cs;

void WithDrawThread1(LPVOID param) {//取900元 EnterCriticalSection(&cs); if ((total-900) >= 0) { total-=900;

cout<<\你取了900元\ } else { cout<<\钱不够了,禁止取钱,马上退卡!\ }

LeaveCriticalSection(&cs); SetEvent(evFin[0]); }

void WithDrawThread2(LPVOID param) {//取700元 EnterCriticalSection(&cs); if ((total-700) >= 0) { total-=700;

cout<<\你取了700元\ } else { cout<<\钱不够了,禁止取钱!\ }

LeaveCriticalSection(&cs); SetEvent(evFin[1]); }

int main(int argc, char* argv[]) { evFin[0] = CreateEvent(NULL,FALSE,FALSE,NULL); evFin[1] = CreateEvent(NULL,FALSE,FALSE,NULL); InitializeCriticalSection(&cs); _beginthread(WithDrawThread1,0,NULL); _beginthread(WithDrawThread2,0,NULL); WaitForMultipleObjects(2, evFin, TRUE, INFINITE); DeleteCriticalSection(&cs); cout<<\余额是\ return 0; }

多运行几次,观察结果并分析。

3、用互斥量(Mutex)来进行线程互斥

? 互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限。

? 由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线

程所访问。

? 当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获

得后得以访问资源。

? 互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资

源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。 用CreateMutex函数创建互斥量: HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全属性结构指针,可为NULL BOOL bInitialOwner, //是否占有该互斥量,TRUE:占有,FALSE:不占有 LPCTSTR lpName //信号量的名称 );

涉及到的其它API如下:

5rn4z4mtaf0c4dl2wbio
领取福利

微信扫码领取福利

微信扫码分享