第2章单机资源共享的应用编程.ppt_第1页
第2章单机资源共享的应用编程.ppt_第2页
第2章单机资源共享的应用编程.ppt_第3页
第2章单机资源共享的应用编程.ppt_第4页
第2章单机资源共享的应用编程.ppt_第5页
已阅读5页,还剩42页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Windows网络编程技术第2章单机资源共享的应用编程,授课老师:胡鸣 数学与计算机学院 计算机系,教学目的,理解进程,线程和动态连接库的概念; 掌握进程的创建方法,进程间的通信; 掌握线程创建的方法,多线程间的通信; 掌握多线程的同步机制; 掌握动态链接库的编写和使用;,本章提纲,2.1 进程间通信 2.1.1进程间通信应用实例及概念 2.1.2进程的创建与终止 2.1.3内存文件映射 2.2 多线程通信 2.2.1多线程应用实例及概念 2.2.2线程的创建、挂起、激活和终止 2.2.3线程的优先级 2.3 同步控制机制 2.3.1同步控制应用实例及意义 2.3.2同步控制类型及应用条件 2

2、.3.3应用实例的算法与实现 2.4 动态链接库 2.4.1静态链接库与动态链接库的应用实例 2.4.2动态链接库的创建和调用方法 2.4.3动态链接库应用的条件,2.1.1进程间通信应用实例及概念,程序和进程 应用程序和进程在概念上是有一定区别的,前者是静态的程序代码,而后者是动态的实体。只有应用程序加载到系统中后才能成为一个进程。 独立进程和共享进程 独立运行的程序称为独立进程 ;另外,应用程序可能启动多个进程,一个进程空间可以运行多个程序,这就是共享进程。 我们以第二章代码“命名管道” 为例来了解进程间通信的基本情况。进程A通过命名管道(后面会介绍)的方式传递两个参数给进程B,进程B将这

3、两个参数进行计算后,把结果通过命名管道的方式返回给进程A。 具体的过程结合下面的图来描述:,进程间通信实例,进程A先启动,然后创建进程B,; 进程B创建成功后先创建命名管道,然后创建并连接一个命名通道。 进程A打开进程B建立的命名管道,同时向命名管道写数据,进程B获取到进程A传过来的数据,进行处理并将结果传回给进程A,2.1.2进程的创建与终止,在上面的例子中,进程A有一个操作是创建进程B。创建进程的函数是:CreateProcess BOOL CreateProcess( LPCTSTR lpApplicationName, / 执行程序文件名 LPTSTR lpCommandLine, /

4、 参数行 LPSECURITY_ATTRIBUTES lpProcessAttributes, / 进程安全参数 LPSECURITY_ATTRIBUTES lpThreadAttributes, / 线程安全参数 BOOL bInheritHandles, / 继承标记 DWORD dwCreationFlags, / 创建标记 LPVOID lpEnvironment, / 环境变量 LPCTSTR lpCurrentDirectory, / 运行该子进程的初始目录 LPSTARTUPINFO lpStartupInfo, / 创建该子进程的相关参数 LPPROCESS_INFORMATI

5、ON lpProcessInformation / 创建后用于被创建子进程的信息 );,进程创建与正常结束,进程是应用程序的执行实例,进程创建成功后,操作系统会给该进程分配私有的虚拟地址空间、代码、数据和其他系统资源;进程结束后,操作系统会回收该进程所占用的系统资源。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间里的数据,这也是我们要学习进程通信的原因。 当主线程的进入点函数返回时,进程也就随之结束。这种进程的终止方式是进程的正常退出,进程中的所有线程资源都能够得到正确的清除。除了这种进程的正常退出方式外,有时还需要在程序中通过代码来强制结束本进程或其他进程的运行。,

6、进程强制结束,1.使用ExitProcess()结束进程 ExitProcess()函数的原型为: voidExitProcess(UINTuExitCode); 2.使用TerminateProcess()结束进程ExitProcess()只能强制执行本进程的退出,如果要在一个进程中强制结束其他进程就要用TerminateProcess()来实现。 BOOLTerminateProcess(HANDLEhProcess,UINTuExitCode); 应该尽可能的让进程正常退出; 在强制终止进程的时候应该考虑资源释放的问题;,2.1.3内存文件映射,前面讲到过,每个进程有自己的地址空间,一个

7、进程不能轻易地访问另一个进程地址空间中的数据,必须采用特殊的方式来进行进程间的通信。本节以第二章代码“内存文件映射”为例介绍利用内存文件映射实现进程间通信。 1、利用内存映射文件进行文件I/O操作,进行文件I/O操作需要下面几个步骤: 步骤一:调用CreateFile()函数,以适当的方式创建或打开一个文件核心对象;如果是内存文件,这一步忽略;,步骤二:把CreateFile()函数返回的文件句柄作为参数,传给CreateFileMapping()函数,由CreateFileMapping()函数创建一个文件映射核心对象的适当属性;如果是内存文件,则句柄是:0 xFFFFFFFF; 步骤三:创

8、建了文件映射核心对象后,调用MapViewOfFile()函数,告诉系统把文件的哪一部分映射到进程的地址空间中,以何种方式映射; 步骤四:利用MapViewOfFile()函数返回的指针来使用文件数据;,步骤五:操作完毕后,调用UnmapViewOfFile()函数,告诉系统撤销对文件映射核心对象的映射; 步骤六:使用CloseHandle()函数关闭文件映射核心对象; 步骤七:使用CloseHandle()函数关闭文件核心对象;,2.2.1多线程应用实例及概念,多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样

9、拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。 多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度,同步等问题。 存钱和取钱的例子,我们利用第二章代码“三线程同步”来说明。,例子,造成这种结果的原因是由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,同时他们可能会同时访问公共变量,导致数据的不一致。如图2.3中,第一行和第二行的结果正常,但是第三行和第四行是线程同时对nResValue进行操作,进

10、程2在0的基础上减10,而进程1则在0的基础上加10,从而导致Result2=-10,Result1=10。这是个错误的结果。也就是说,在同一时刻,只能有一个线程对nResValue操作才是正确的,也只有这样才不会出现上面的情况。后面会专门讲这个问题同步控制。,2.2.2线程的创建、挂起、激活和终止,一个进程的主线程是由操作系统自动生成的,如果让一个主线程创建额外的子线程,可以通过CreateThread函数来完成。若线程创建成功,返回值为新线程的句柄;失败则返回NULL。 在创建的线程的时候,可以指定线程的初始状态。线程在创建的时候,有两种状态可以指定,执行状态和挂起状态。通过设置Creat

11、eThread的第五个参数可以设置线程的初始状态,当参数为时,线程就会立即执行,而参数设置为CREATE_SUSPENDED时,线程创建后并不马上执行,而是被挂起。要想该线程能够执行,必须在主线程或者其他的线程里调用ResumeThread函数并传递给它调用CreateThread时返回的线程句柄来激活该进程。,线程的挂起与激活,除了在创建线程的时候可以挂起线程,我们还可以通过函数SuspendThread来挂起线程,一个线程可以被挂起多次。线程可以自行暂停运行,但是不能自行恢复运行。如果一个线程被挂起n次,则该线程也必须被恢复n次才可能得以执行,该SuspendThread的声明如下 : D

12、WORD SuspendThread(HANDLE hThread); 其中hThread是线程句柄 激活一个线程时,系统会先检查线程挂起的次数,如果挂起的次数为,则表示该线程并非处于挂起状态,否则,该线程的挂起次数值将被减。ResumeThread函数很简单: DWORD ResumeThread(HANDLE hThread),其中hThread是线程句柄。,线程的终止与休眠,当要中止一个线程的时候,类似于进程的中止。线程的终止有如下四种方式: (1)线程函数返回; (2)线程自身调用ExitThread 函数即终止自己 ; (3)同一进程或其他进程的线程调用TerminateThread

13、函数 ; (4)包含线程的进程终止。 VOID Sleep(DWORD dwMilliseconds); 该函数可使线程暂停自己的运行,直到dwMilliseconds毫秒过去为止。它告诉系统,自身不想在某个时间段内被调度。,2.2.3线程的优先级,线程是有一定的优先级,操作系统会根据线程的优先级进行调度,分配CPU时间片,一个线程的优先级首先属于一个类,其实就是一个进程,然后是在该类中的相对位置。线程的优先级的计算如下: 线程优先级 = 进程类基本优先级 + 线程相对优先级 进程类的基本优先级包括: (1)实时:REALTIME_PRIORITY_CLASS; (2)高:HIGH _PRIO

14、RITY_CLASS; (3)高于正常:ABOVE_NORMAL_PRIORITY_CLASS; (4)正常:NORMAL _PRIORITY_CLASS; (5)低于正常:BELOW_ NORMAL _PRIORITY_CLASS; (6)空闲:IDLE_PRIORITY_CLASS。,实例图,线程的相对优先级,(1)空闲:THREAD_PRIORITY_IDLE; (2)最低线程:THREAD_PRIORITY_LOWEST; (3)低于正常线程:THREAD_PRIORITY_BELOW_NORMAL; (4)正常线程:THREAD_PRIORITY_ NORMAL (缺省); (5)高

15、于正常线程:THREAD_PRIORITY_ABOVE_NORMAL; (6)最高线程:THREAD_PRIORITY_HIGHEST; (7)关键时间:THREAD_PRIOTITY_CRITICAL。,控制线程的优先级,通常,应用程序可以使用下列方法控制线程的相对优先级: 当使用CreateProcess时,可以指定进程的优先级;若未指定,缺省优先级为正常; 可以使用SetPriotityClass来改变进程的优先级。这将影响到进程内的所有线程的优先级,但是线程的相对优先级不会变; 可以通过SetThreadPriority来任何一个线程的优先级; 当一个线程首次被创建时,它的优先级类等同

16、于它所属于的进程优先级类。,2.3.1同步控制应用实例及意义,进程中的所有线程共享进程的虚拟地址空间,这意味着所有线程都可以访问进程中的资源,一方面,这种机制为我们的编程带来方便,但同时也会带来访问冲突,其结果会导致数据的错乱。线程的同步是为了协调多个线程的执行,保证数据完整性,以第二章代码“三线程同步”为例。 多线程编程一个最具挑战性的问题就是:如何让一个线程与另一个线程合作。 当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去,这就是所谓的同步。如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是异步。Win32API中的SendMassage

17、()就是同步行为,而PostMassage()就是异步行为,如图所示,同步与异步,两个例子的比较,2.3.2同步控制类型及应用条件,线程的同步是通过同步对象来实现的。同步对象是一个数据结构,用来协调多线程的执行,它可以被多个线程共享。 同步对象主要有五种:临界区域(Critical Section)、互斥信号(Mutex)、信号量(Semaphone)、事件对象(Event)和互锁变量(Interlocked)。 两个重要的函数:监测单个同步对象的WaitForSingleObject()函数和可以同时监测多个同步对函数WaitForMultipleObjects() 象,两个重要的函数,DW

18、ORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); 参数hHandle是同步对象的句柄。 参数dwMilliseconds是以毫秒为单位的超时间隔,如果该参数为0,那么函数就测试同步对象的状态并立即返回,如果该参数为INFINITE,则超时间隔是无限的。 DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds ); 参数nCount是句柄数组中句柄的数目。 参数lpHan

19、dles代表一个句柄数组。 参数bWaitAll说明了等待类型,如果为TRUE,那么函数在所有对象都有信号后才返回,如果为FALSE,则只要有一个对象变成有信号的,函数就返回。 参数dwMilliseconds是以毫秒为单位的超时间隔,如果该参数为0,那么函数就测试同步对象的状态并立即返回,如果该参数为INFINITE,则超时间隔是无限的。,临界区域(Critical Sections),临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其他的线程要等待,直到该线程释放临界

20、区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。 所谓临界区域就是指一块“用来处理一份被共享资源”的程序代码。用户模式下资源每一次只能一个进程使用,以第二章代码“临界区”为例。,临界区域相关函数,对类型为CRITICAL_SECTION的局部变量初始化,方法是调用函数Void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection ); 当使用完临界区时,清除它使用函数void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSectio

21、n ); 进入临界区时,使用函数void EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection ); 离开临界区时,使用函数void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );,互斥信号(Mutex),互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在

22、任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。 牺牲速度增加弹性,它是在内核中锁住变量(开销大),可以跨进程使用,等待mutex可以指定等待时间,以第二章代码“互斥信号”为例。,互斥信号相关函数,产生Mutex,调用函数HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName ); 关闭Mutex,

23、使用函数BOOL CloseHandle( HANDLE hObject); 打开一个互斥信号,调用函数HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ); 获得mutex所有权可以使用Win32的Wait函数。 释放mutex,调用函数BOOL ReleaseMutex( HANDLE hMutex );,信号量(Semaphore),信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数

24、目,以第二章代码“信号量”为例。 信号量锁住同类一定数量的资源 信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。,信号量相关函数,产生信号量,调用函数HANDLE CreateSemaphore( LPSECURITY_AT

25、TRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); 获得信号量,可以使用Win32的Wait函数。 释放信号量,调用函数BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); 关闭信号量,可以使用Closehandle()函数,事件对象(Event Object),事件(Event)为一最具有弹性的核心对象,是WIN32提供的最灵活的线程间同步方式,

26、事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false) ),这两种状态全由程序控制,不会成为Wait函数的副作用。它是用来通知其他进程/线程某个操作已经完成。根据状态变迁方式的不同,事件可分为两类: (1)手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。当有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功,因为该事件的状态不会被自动重置。 (2)自动恢复:一旦事件发生并被处

27、理后,自动恢复到没有事件状态,不需要再次设置。使用这种方式,则等待的线程总只有一个等待成功,因为任何一个线程等待成功,事件会自动恢复到无信号状态,从而阻塞其他等待线程。,事件对象相关函数,HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPTSTR lpName); 产生事件对象 利用Win32的Wait函数传递事件信息。 设为激发态,BOOL SetEvent( HANDLE hEvent ); 设为非激发态, BOOL ResetEvent

28、( HANDLE hEvent ); BOOL PulseEvent( HANDLE hEvent ); 如果为人工设置,把事件设为激发态唤醒所有等待线程,然后把事件恢复为非激发态。如果为自动设置,把事件设为激发态,然后事件恢复为非激发态 HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ); 打开一个现有命名的对象,互锁变量(Interlocked Variable),互锁变量函数是使用的比较多的线程同步方法,互锁函数的家族十分的庞大,有兴趣的读者可以查查MSDN,以InterLocked

29、开始的函数都是户数函数。使用互锁函数的优点是:他的速度要比其他的CriticalSection, Mutex, Event, Semaphore快很多。 同步机制最简单类型,对标准的32位变量操作,互锁变量相关函数,LONG InterlockedIncrement( LPLONG lpAddend ); 和LONG InterlockedDecrement( LPLONG lpAddend ); 变量经过运算(加1或减1运算)后,等于0,传回0;大于0传回正值;小于0,传回负值。引用计数处理。 LONG InterlockedExchange( LPLONG Target, LONG Val

30、ue ); 设定一个新值传回旧值。 LONG InterlockedCompareExchange( LONG volatile* Destination, LONG Exchange, LONG Comperand );原子方式比较赋值,即如果目标变量和被比较值相等时,目标变量才会被赋值为原始值。,2.3.3应用实例的算法与实现,所有的互锁函数都是跟写变量相关的,没有读变量的互锁函数。因为读变量不会产生同步问题。 临界区适用范围是单进程的各线程之间,它是一个局部对象,不是核心对象;快速而又效率;不能同时有一个以上的临界区被等待;无法侦测是否已被某个线程放弃。 Mutex适用不同线程(可以属于

31、不同进程)之间为核心对象;如果拥有它的那个线程结束,产生一个“abandoned”错误信息;可以使用Wait等待mutex;可以具名,为其他进程开启;只能被拥有它的那个线程释放。,Semaphore用来追踪有限资源,它是核心对象;没有拥有者;可以具名,为其他进程开启;可以被任何线程释放。 事件对象通常使用于异步I/O,或自定义的同步对象,是核心对象;完全在程序的掌控之下;适用于设计新的同步对象;“要求苏醒”的请求不会被储存起来,可能会遗失掉;可以具名,为其他进程开启 Interlocked Variable使用所谓的spin-lock。所谓spin-lock是一种忙循环,在极短时间内执行,除此

32、之外,用于计数。允许对4个字节的数值进行同步操作,不需要其他互斥机制;在SMP操作系统中亦可有效运作。,2.4.1静态链接库与动态链接库的应用实例,我们可以创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从文件中查找符合要求的函数和变量进行链接,整个查找过程根本不需要我们操心。这个文件叫做 “库(Libary)”,平时我们把编译好的目标代码存储到“库”里面,要用的时候链接程序帮我们从库里面把相应的函数找出来。 以第二章代码“动态链接库”为例,静态和动态链接库,静态库的两个特点: 链接后产生的可执行文件包含了所有需要调用的函数的代码,因此占用磁盘空间

33、较大。 如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。 动态链接库就是为了解决这些问题而诞生的技术,顾名思义,动态链接的意思就是在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码。,静态和动态绑定,根据载入程序何时确定动态代码的逻辑地址,可以把动态装载分为两类。 静态绑定(static binding) 使用静态绑定的程序一开始载入内存的时候,载入程序就会把程序所有调用到的动态代码的地址算出确定下来,这种方式使程序刚运行的初始化时间较长,不过一旦完成动态装载,程序的运

34、行速度就很快。 动态绑定(dynamic binding) 使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态绑定的程序。,2.4.2动态链接库的创建和调用方法,DLL代码,#ifndef LIB_H #define LIB_H extern C int _declspec(dllexport)add(int x, int y);/函数导出 extern C int _declspec(dllexport)sub(int x, int y); #endif /* 文件名:lib.cpp*/ #include lib.h int add(int x, int y) return x + y; int sub(int x,int y) return x -y; ,调用DLL的代码,#include #include

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论