第10章 c  多线程—武汉科技大学研究生_第1页
第10章 c  多线程—武汉科技大学研究生_第2页
第10章 c  多线程—武汉科技大学研究生_第3页
第10章 c  多线程—武汉科技大学研究生_第4页
第10章 c  多线程—武汉科技大学研究生_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

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

文档简介

多线程,- 2 -,本章目标,了解进程和线程的区别掌握多线程程序的应用场合掌握利用API创建线程的方法掌握利用MFC创建界面线程和工作者线程的方法掌握使用全局变量和自定义消息实现线程通信的方法掌握使用CEvent类、CCriticalSection类、CSemaphore类实现线程同步的方法,- 3 -,线程就是操作系统分配处理器时间的最基本单元。在一个多线程的应用程序中,每一个线程都有它自己的堆栈,并且可以独立地操作在同一程序中运行的其它线程。,一个应用程序至少有一个线程,即程序的基本或主线程。我们可以根据需要启动和停止其它附加线程,但是一旦主线程停止了,整个程序就被关闭了。只要程序还在运行,主线程就在运行。,多线程编程基础进程和线程,- 4 -,多线程编程基础进程和线程,应用程序在Windows操作系统上是以单独的进程运行的,每一个新启动的应用程序都会创建一个新的进程线程是进程内部的一个执行单元,它是操作系统分配处理器时间的基本单位从程序员的角度看,进程即一个可执行程序;线程是进程内的一个可以被操作系统独立调度执行的函数,- 5 -,多线程编程基础使用多线程的意义,像网络通信程序这样既要进行耗时的工作(远程数据收发),又要保持对用户输入(键盘和鼠标)响应,使用多线程是最佳选择。一些大型服务程序,如数据库系统,网络游戏服务器程序 ,使用多线程将赋予应用程序强大的控制能力和执行能力。,- 6 -,Windows多线程编程,Windows多线程编程也可以使用两种方法:直接使用Win32 API或MFC线程技术。使用Win32 API的好处是操作非常灵活,而MFC多线程编程较为简便,特别是方便实现界面线程。在MFC中,线程分为用户界面线程和工作者线程两种。用户界面线程拥有自己的消息队列和消息循环来处理界面消息,可以与用户进行交互。工作者线程没有消息循环,一般用来完成后台工作(例如自动保存、从网络中自动获取数据等)。,- 7 -,多线程编程API多线程编程,与线程操作有关的API函数,- 8 -,多线程编程API多线程编程,创建线程,#include /定义线程函数DWORD WINAPI ThreadFun(LPVOID lpParameter).return 0;int main(int argc,char* argv)/创建线程CreateThread(NULL,0,ThreadFun,0,0,0);.,- 9 -,多线程编程MFC界面线程,MFC的界面线程其实就是拥有消息循环和窗体的线程,当程序中需要多个窗口,而有的窗口需要包含“费时”的操作,这时需要创建界面线程。 MFC创建界面线程需要用到CWinThread类。,- 10 -,多线程编程MFC界面线程,1. CWinThread类,- 11 -,多线程编程MFC界面线程,2.用户界面线程的创建过程:从CWinThread派生自定义类;在类定义文件中添加DECLARE_DYNCREATE宏,在类实现文件中添加IMPLEMENT_DYNCREATE宏,这两个宏配合使用,以实现类的动态创建对象功能;在派生类中重写InitInstance()函数,在函数中创建用户界面窗口,并将窗口指针赋值m_pMainWnd,该函数在线程创建时,被自动调用;如果需要,可以在派生类中重写ExitInstance()函数,做一些必要的清理工作,在线程退出前该函数被自动调用。,#,12,一.创建线程:原理,为了使用MFC创建一个线程,我们所做的就是编写一个希望的和程序的其它部分同时运行的函数,然后调用AfxBeginThread()来启动一个用以执行我们的函数的线程。只要线程的函数在运行,线程就存活着,当线程函数结束时,线程就被销毁。,#,13,方法1,第一种形式用于创建工作者线程的AfxBeginThread()函数如下所示:CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );,#,14,方法2,第二种线程用于创建用户接口线程AfxBeginThread()函数如下所示:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );,#,15,参数意义,这两种形式的函数的返回值是新创建的线程对象的指针。 参数意义如下:pfnThreadProc:指向工作者线程的控制函数的指针,它不能是NULL。此控制函数必须声明成如下样式:UINT MyControllingFunction( LPVOID pParam );pThreadClass:从CWinThread派生的RUNTIME_CLASSpParam:传递给工作者线程的控制函数的参数nPriority:线程的期望的优先权。如果这个值为0,则新线程和创建线程具有同样的优先级。nStackSize:以字节为单位定义了新线程的堆栈大小。如果这个值为0,则新线程和创建线程具有同样大小的堆栈。dwCreateFlags:控制线程创建的附加标志。这个值可以是以下两个值中的一个:CREATE_SUSPENDED和0。如果是标志是前者,以挂起数为1启动线程。只有在ResumeThread被调用时,这个线程才会被执行。如果标志为0,则在创建线程后立即执行线程。lpSecurityAttrs:指向定义了线程安全属性的SECURITY_ATTRIBUTES结构的指针。如果为NULL,则新线程和创建线程具有同样的安全属性。,#,16,线程的优先级,THREAD_PRIORITY_ABOVE_NORMAL 比正常优先级高一个级别THREAD_PRIORITY_BELOW_NORMAL 比正常优先级低一个级别THREAD_PRIORITY_HIGHEST 比正常优先级高两个级别THREAD_PRIORITY_IDLE 基本优先级为1。对于REALTIME_PRIORITY_CLASS进程,优先级为16THREAD_PRIORITY_LOWEST 比正常优先级低两个级别THREAD_PRIORITY_NORMAL 正常优先级别THREAD_PRIORITY_TIME_CRITICAL 基本优先级为15。对于REALTIME_PRIORITY_CLASS进程,优先级别是30。一个线程的优先级决定了相对于其它正在运行的线程这个线程控制系统的时间。通常,线程的级别越高,它的运行时间也越长,这也正是THREAD_PRIORITY_TIME_CRITICAL如此高的原因。,- 17 -,多线程编程MFC界面线程,3.用户界面线程的启动,有两种方法:使用全局函数AfxBeginThread()。创建CWinThread对象,而后调用CreateThread()成员函数。,- 18 -,多线程编程MFC工作者线程,创建工作者线程时,通常无需从CWinThread派生新的线程类,只需要提供一个线程入口,也就是一个函数地址即可。,/线程函数的固定原型是:UINT _cdecl Function(LPVOID pParam);,/AfxBeginThread()函数创建工作者线程的原型如下:CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );,- 19 -,线程间的通信,有两种方法可以完成线程通信:使用全局变量使用自定义消息,- 20 -,线程间的通信使用全局变量,由于属于同一个进程的各个线程共享操作系统分配该进程的资源,因此解决线程间通信最简单的一种方法是使用全局变量。方法就是在进程内定义一个全局变量用于线程间的通信处理。,- 21 -,线程间的通信使用自定义消息,一个线程可以向另外一个线程发送消息,以实现线程间的通信。其中,接收消息的线程必须是界面线程,因为只有界面线程才有消息循环。发送消息的线程既可以是工作者线程,也可以是界面线程,- 22 -,线程间的通信使用自定义消息,发送消息可以调用以下几个API函数 :SendMessage():向某个窗体发送消息,调用该函数前需要提前获取窗口的句柄。接收消息的窗口处理完消息后,该函数才能返回。PostMessage():向某个窗体投递消息,调用该函数前需要提前获取窗口的句柄。不管接收消息的窗口是否处理完消息,该函数都立刻返回,即不等待消息处理完毕。PostThreadMessage():向某个线程发送消息,调用该函数前需要提前获取线程的ID。,#,23,使用全局变量通信,实验题目(1) 使用全局变量通信 如需要主程序能够停止线程。需要一个告诉线程何时停止的方法。一种方法是建立一个全局变量,然后让线程监督这个全局变量是否为标志线程终止的值。为了实现这种方法,按照如下步骤修改前面创建的Thread程序。1. 在“线程”菜单中添加菜单项“停止线程”,ID为ID_STOPTHREAD。2. 为“停止线程”添加消息处理函数OnStopthread()。3. 在ThreadView.cpp文件中添加一个全局变量threadController。添加方法是在ThreadView.cpp的最上面,在endif下面添加下面的语句:volatile int threadController;关键字volatile表示我们希望这个变量可以被外面使用它的线程修改。,#,24,使用全局变量通信,4. 修改OnStartthread()函数,代码如下所示:void CThreadView:OnStartthread() / TODO: Add your command handler code herethreadController = 1;HWND hWnd = GetSafeHwnd();AfxBeginThread(ThreadProc, hWnd, THREAD_PRIORITY_NORMAL);threadController的值决定线程是否继续。,#,25,使用全局变量通信,5. 在OnStopthread()函数中添加下列代码:threadController = 0;6. 修改ThreadProc()函数的代码,代码如下:UINT ThreadProc(LPVOID param) :MessageBox(HWND)param, Thread activated., Thread, MB_OK); while (threadController = 1) ; :MessageBox(HWND)param, Thread stopped., Thread, MB_OK); return 0; 现在线程首先显示一个消息框,告诉用户线程被启动了。然后通过一个while循环检查全局变量threadController,等待它的值变成0。while循环中可以加上执行你希望的任务的代码。,#,26,使用用户自定义消息通信,现在你有了一个简单的用于从主线程中联系附加线程的方法。反过来,如何从附加线程联系主线程呢?最简单的实现这种联系的方法是在程序中加入用户定义的Windows消息。一. 定义用户消息:const WM_USERMSG = WM_USER + 100;WM_USER变量是由Windows定义的,它是第一个有效的用户消息数。因为我们的程序的其它部分也会使用用户消息,故将新的用户消息WM_USERMSG设置为WM_USER+100。二. 在线程中调用:PostMessage()函数来向主线程发送我们所需要的消息。一般按照下面的方式调用:PostMessage()函数::PostMessage(HWND)param, WM_USERMSG, 0, 0);PostMessage()的四个参数分别是接收消息的窗口的句柄、消息的ID、消息的WPARAM和LPARAM参数。,#,27,使用用户自定义消息通信,将下面的语句加入到ThreadView.h中CThreadView类声明的上面。const WM_THREADENDED = WM_USER + 100; 仍然是在此头文件中,在消息映射中加入下列语句,注意要加到AFX_MSG的后面和DECLARE_MESSAGE_MAP的前面。 afx_msg LONG OnThreadended(); 打开ThreadView.cpp,在类的消息映射中加入下列语句,位置在AFX_MSG_MAP之后。,#,28,使用用户自定义消息通信,ON_MESSAGE(WM_THREADENDED, OnThreadended)再用下面的语句更改ThreadProc()函数。UINT ThreadProc(LPVOID param):MessageBox(HWND)param, Thread activated., Thread, MB_OK); while (threadController = 1) ; :PostMessage(HWND)param, WM_THREADENDED, 0, 0);return 0;,#,29,使用用户自定义消息通信,在CThreadView中添加下面的成员函数。LONG CThreadView:OnThreadended(WPARAM wParam, LPARAM lParam)AfxMessageBox(Thread ended.);return 0;,#,30,使用Event对象通信,下一个在两个线程间通信的方法是使用Event对象,在MFC下也就是CEvent类对象。一个Event对象可以有两种状态:通信状态和非通信状态。线程监视着Event对象的状态,并由此在合适的时间执行它们的操作。创建一个CEvent类的对象,如下:CEvent threadStart;CEvent的构造函数形式如下:CEvent( BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );,#,31,使用Event对象通信,当CEvent对象被创建之后,它自动的处在未通信状态。为了使其处在通信状态,调用其成员函数SetEvent(),如下所示: threadStart.SetEvent(); 在执行完上述语句之后,threadStart将处在其通信状态。你的线程应当监视它,这样才能知道何时执行。线程是通过调用如下Windows API函数WaitForSingleObject()来监视CEvent对象的,形式如下::WaitForSingleObject(threadStart.m_hObject, INFINITE);预定义的常量INFINITE告诉WaitForSingleObject()直到指定的CEvent对象处在通信状态时才返回。换句话说,如果你把WaitForSingleObject()放在线程的开头,系统将挂起线程直到CEvent对象处在通信状态。当主线程准备好后,你应当调用SetEvent()函数。,#,32,使用Event对象通信,一旦线程不再挂起,它就可以运行了。但是,如果此时我们还想和线程通信,线程必须监视下一个CEvent对象处在通信状态,故我们需要再次调用WaitForSingleObject()函数,此时需要将等待时间设置为0,如下所示: :WaitForSingleObject(threadend.m_hObject, 0);在这种情况下,如果WaitForSingleObject()返回值为WAIT_OBJECT_0,则CEvent对象处在通信状态。否则,CEvent对象处在非通信状态。,#,33,使用Event对象通信:例子,下面的例子说明如何使用CEvent类在两个线程间通信。按照以下步骤进行:1. 在ThreadView.cpp中#include ThreadView.h语句后面加上#include afxmt.h。2. 在ThreadView.cpp中volatile int threadController语句后加上下列语句:CEvent threadStart;CEvent threadEnd;删除语句volatile int threadController。,#,34,使用Event对象通信:例子,3. 用下面的代码更换ThreadProc()函数。UINT ThreadProc(LPVOID param):WaitForSingleObject(threadStart.m_hObject, INFINITE);:MessageBox(HWND)param, Thread activated., Thread, MB_OK);BOOL keepRunning = TRUE;while (keepRunning)int result = :WaitForSingleObject(threadEnd.m_hObject, 0);if (result = WAIT_OBJECT_0)keepRunning = FALSE;:PostMessage(HWND)param, WM_THREADENDED, 0, 0); return 0;,#,35,使用Event对象通信:例子,4. 用下面的语句替换OnStartthread()函数中的内容。threadStart.SetEvent();5. 用下面的语句替换OnStopthread()函数中的内容。threadEnd.SetEvent();6. 使用ClassWizard为CthreadView处理WM_CREATE消息的函数OnCreate(),并在TODO后面添加代码。OnCreate()函数如下所示:int CThreadView:OnCreate(LPCREATESTRUCT lpCreateStruct) if (CView:OnCreate(lpCreateStruct) = -1)return -1;/ TODO: Add your specialized creation code hereHWND hWnd = GetSafeHwnd();AfxBeginThread(ThreadProc, hWnd);return 0;,- 36 -,线程同步MFC线程同步类,同步对象类包括:CCriticalSection(临界区)CEvent(事件)CSemaphore(信号量)CMutex(互斥)同步访问类包括:CMultiLockCSingleLock。,#,37,线程同步,如何防止两个线程在同一时间访问同一数据?例如,假设一个线程正在更新一个数据集,而同时另外一个线程正在读取数据集,结果如何?第二个线程将会读取到错误的数据,因为数据集中只有一部分元素被更新过。保持在同一个进程内的线程工作协调一致称之为线程同步。Event对象实际上就是线程同步的一种形式。三种使多线程程序更安全的线程同步对象临界区(critical section)、互斥对象(mutex)、信号量(semaphore)。,#,38,(1) 使用Critical Section,Critical Section是一种保证在一个时间只有一个线程访问数据集的非常简单的方法。当你使用Critical Section,你给了线程一个它们必须共享的对象。任何拥有Critical Section对象的线程可以访问被保护起来的数据。其它线程必须等待直到第一个线程释放了Critical Section对象,此后其它线程可以按照顺序抢占Critical Section对象,访问数据。因为线程只有拥有Critical Section对象才能访问数据,而且在一个时刻只有一个线程可以拥有Critical Section对象,所以决不会出现一个时刻有多个线程访问数据。,#,39,(1) 使用Critical Section,为了在MFC程序中创建一个Critical Section对象,你应当创建CcriticalSection类的对象,如下所示: CCriticalSection criticalSection当程序代码准备访问你保护的数据时,调用CCriticalSection的成员函数Lock(), criticalSection.Lock();如果没有其他线程没有拥有criticalSection,Lock()将criticalSection给调用它的线程。这个线程便能够访问受保护的数据,此后它调用CcriticalSection的成员函数Unlock(): criticalSection.Unlock();Unlock()释放了对criticalSection的拥有权,这样其它线程就可以占有它并访问受保护的数据。最好的方法是将数据放在线程安全类中。当你这样做后,你不用担心在主线程中的线程同步,线程安全类会替你处理的。,#,40,(1) 使用Critical Section,如CcountArray类的执行文件。#include stdafx.h#include CountArray.hvoid CCountArray:SetArray(int value)criticalSection.Lock();for (int x=0; x10; +x)arrayx = value;criticalSection.Unlock();void CCountArray:GetArray(int dstArray10)criticalSection.Lock();for (int x=0; x10; +x)dstArrayx = arrayx;criticalSection.Unlock();,#,41,(1) 使用Critical Section,void CThreadView:OnStartthread() / TODO: Add your command handler code here HWND hWnd = GetSafeHwnd(); AfxBeginThread(WriteThreadProc, hWnd);/ WriteThreadProc函数调用了CCountArray:SetArray(int value)方法 AfxBeginThread(ReadThreadProc, hWnd); / WriteThreadProc函数调用 /了CCountArray:GetArray(int dstArray10)方法 /注意:CCountArray countArray是全局变量,对两个线程都可见,#,42,(2) 使用Mutex(互斥对象),互斥对象有点象critical section,但它不仅允许同一程序的线程之间,而且允许不同程序的线程(即不同进程)之间共享资源。下面是一个实例CCountArray2类的头文件。#include afxmt.hclass CCountArray2private:int array10;CMutex mutex;public:CCountArray2() ;CCountArray2() ;void SetArray(int value);void GetArray(int dstArray10);,#,43,(2) 使用Mutex(互斥对象),下面是CCountArray2的执行文件,尽管互斥对象和critical section提供相同的服务,但是二者使用起来还是有很多不同的。#include stdafx.h#include CountArray2.hvoid CCountArray2:SetArray(int value)CSingleLock singleLock(,#,44,(2) 使用Mutex(互斥对象),为了访问一个互斥对象,须创建一个CSingleLock对象或一个CMultiLock对象,由它们来执行实际上的访问控制。 这里使用CSingleLock对象,因为这个类只处理单一的互斥对象。当代码准备操作受保护的资源,你应当创建一个CSingleLock对象,如下所示: CSingleLock singleLock(如果互斥对象不被任何线程拥有,调用上述语句的线程将拥有该互斥对象。如果另外一个程序已经占有了互斥对象,系统将挂起调用上述语句的线程直到互斥对象被释放,此时被挂起的线程被唤醒并且占有互斥对象。 为了释放这个互斥对象,你应该调用CSingleLock对象的成员函数Unlock()。然而,如果你是在栈中创建的CSingleLock对象,就不必调用Unlock()。当函数结束后,该对象超出作用范围,这将使其析构函数执行。析构函数将释放互斥对象。,#,45,(3) 使用信号量(Semaphore),在MFC程序中使用信号量和使用critical section和互斥对象相差不多,但是功能却不大相同。信号量允许多个线程同时访问资源,但必须是同一点。当你创建了信号量。你应当告诉它同一时刻有多少线程访问它。这样,每次一个线程抢占资源,信号量减小它内部的计数器。当计数器为0时,不会再有其它线程被允许访问资源直到有释放了资源使计数器增加。在创建信号量时应当设置计数器的初始值和最大值,如下所示:CSemaphore Semaphore(2, 2);,#,46,(3) 使用信号量(Semaphore),一旦创建了信号量对象,就开始计算资源访问。为了实现资源访问,首先应当创建一个CSingleLock对象,给它信号量的指针,如下: CSingleLock singleLock(semaphore); 为了减小信号量的计数器,应当调用CSingleLock的成员函数Lock(), singleLock.Lock();此时,信号量减小了内部的计数器。这个新的数目保持有效直到信号量被释放。 通过调用CSingleLock的成员函数Unlock(),使信号量的计数器增加。 singleLock.Unlock();,- 47 -,线程同步MFC线程同步类,同步对象类的选择方法如下:若同时只能有一个线程使用此资源,则使用CCriticalSection。若应用程序必须等到发生某事件才能访问资源(例如,在将数据写入文件之前,必须先从通信端口接收它),则使用CEvent。若同一应用程序内一个以上的线程可以同时访问资源(例如,应用程序允许在同一文档上最多同时打开五个带有视图的窗口),则使用CSemaphore。,- 48 -,线程同步MFC线程同步类,同步访问类的选择方法如下 :如果应用程序只与访问单个受控资源有关,则使用CSingleLock。如果需要访问多个受控资源中的任何一个,则使用CMultiLock。,- 49 -,线程同步CriticalSection类,CriticalSection类提供了对临界区对象的支持,用法如下: 用CCriticalSection类定义全局对象或在线程执行期间一直可以访问的类数据成员。在访问需要保护的资源之前,定义CSingleLock对象,并将

温馨提示

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

评论

0/150

提交评论