这是我做的网站吗,该如何选择深圳网站建设公司,一个网站怎么做软件,张家港网站建设服务原地址#xff1a;http://www.cnblogs.com/wxfasdic/archive/2010/09/23/1833522.html留个纪念#xff0c;不错的总结。十个例子清晰列举啦多线程编程的奥妙。 VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好. 一、问题的提出编写一个耗时的单线程程序#…原地址http://www.cnblogs.com/wxfasdic/archive/2010/09/23/1833522.html 留个纪念不错的总结。十个例子清晰列举啦多线程编程的奥妙。 VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好. 一、问题的提出 编写一个耗时的单线程程序 新建一个基于对话框的应用程序SingleThread在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮ID为IDC_SLEEP_SIX_SECOND标题为 “延时6秒”添加按钮的响应函数代码如下 void CSingleThreadDlg::OnSleepSixSecond() { Sleep(6000); //延时6秒 } 编译并运行应用程序单击“延时6秒”按钮你就会发现在这6秒期间程序就象“死机”一样不在响应其它消息。为了更好地处理这种 耗时的操作我们有必要学习——多线程编程。 二、多线程概述 进程和线程都是操作系统的概念。进程是应用程序的执行实例每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成 进程在运行过程中创建的资源随着进程的终止而被销毁所使用的系统资源在进程终止时被释放或关闭。 线程是进程内部的一个执行单元。系统创建好进程后实际上就启动执行了该进程的主执行线程主执行线程以函数地址形式比如说 main或WinMain函数将程序的启动点提供给Windows系统。主执行线程终止了进程也就随之终止。 每一个进程至少有一个主执行线程它无需由用户去主动创建是由系统自动创建的。用户根据需要在应用程序中创建其它线程多个线 程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中共同使用这些虚拟地址空间、全局变量和系统资源所 以线程间的通讯非常方便多线程技术的应用也较为广泛。 多线程可以实现并行处理避免了某项任务长时间占用CPU时间。要说明的一点是目前大多数的计算机都是单处理器CPU的为了运 行所有这些线程操作系统为每个独立线程安排一些CPU时间操作系统以轮换方式向线程提供时间片这就给人一种假象好象这些线程都在 同时运行。由此可见如果两个非常活跃的线程为了抢夺对CPU的控制权在线程切换时会消耗很多的CPU资源反而会降低系统的性能。这一 点在多线程编程时应该注意。 Win32 SDK函数支持进行多线程的程序设计并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C 6.0中使用MFC类库也实现了多线程的程序设计使得多线程编程更加方便。 三、Win32 API对多线程编程的支持 Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。 1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId); 该函数在其调用进程的进程空间里创建一个新的线程并返回已建线程的句柄其中各参数说明如下 lpThreadAttributes指向一个 SECURITY_ATTRIBUTES 结构的指针该结构决定了线程的安全属性一般置为 NULL dwStackSize指定了线程的堆栈深度一般都设置为0 lpStartAddress表示新线程开始执行时代码所在函数的地址即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc ThreadFunc 是线程函数名 lpParameter指定了线程执行时传送给线程的32位参数即线程函数的参数 dwCreationFlags控制线程创建的附加标志可以取两种值。如果该参数为0线程在被创建后就会立即开始执行如果该参数为 CREATE_SUSPENDED,则系统产生线程后该线程处于挂起状态并不马上执行直至函数ResumeThread被调用 lpThreadId该参数返回所创建线程的ID 如果创建成功则返回线程的句柄否则返回NULL。 2、DWORD SuspendThread(HANDLE hThread); 该函数用于挂起指定的线程如果函数执行成功则线程的执行被终止。 3、DWORD ResumeThread(HANDLE hThread); 该函数用于结束线程的挂起状态执行线程。 4、VOID ExitThread(DWORD dwExitCode); 该函数用于线程终结自身的执行主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。 5、BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode); 一般情况下线程运行结束之后线程函数正常返回但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下 hThread将被终结的线程的句柄 dwExitCode用于指定线程的退出码。 使用TerminateThread()终止某个线程的执行是不安全的可能会引起系统不稳定虽然该函数立即终止线程的执行但并不释放线程所占 用的资源。因此一般不建议使用该函数。 6、BOOL PostThreadMessage(DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam); 该函数将一条消息放入到指定线程的消息队列中并且不等到消息被该线程处理时便返回。 idThread将接收消息的线程的ID Msg指定用来发送的消息 wParam同消息有关的字参数 lParam同消息有关的长参数 调用该函数时如果即将接收消息的线程没有创建消息循环则该函数执行失败。 四、Win32 API多线程编程例程 例程1 MultiThread1 建立一个基于对话框的工程MultiThread1在对话框IDD_MULTITHREAD1_DIALOG中加入两个按钮和一个编辑框两个按钮的ID分别是IDC_START IDC_STOP 标题分别为“启动”“停止”IDC_STOP的属性选中Disabled编辑框的ID为IDC_TIME 属性选中Read-only 在MultiThread1Dlg.h文件中添加线程函数声明 void ThreadFunc(); 注意线程函数的声明应在类CMultiThread1Dlg的外部。在类CMultiThread1Dlg内部添加protected型变量 HANDLE hThread; DWORD ThreadID; 分别代表线程的句柄和ID。 在MultiThread1Dlg.cpp文件中添加全局变量m_bRun volatile BOOL m_bRun; m_bRun 代表线程是否正在运行。 你要留意到全局变量 m_bRun 是使用 volatile 修饰符的volatile 修饰符的作用是告诉编译器无需对该变量作任何的优化即无需将它放到一个寄存器中并且该值可被外部改变。对于多线程引用的全局变量 来说volatile 是一个非常重要的修饰符。 编写线程函数 void ThreadFunc() { CTime time; CString strTime; m_bRunTRUE; while(m_bRun) { timeCTime::GetCurrentTime(); strTimetime.Format(%H:%M:%S); ::SetDlgItemText(AfxGetMainWnd()-m_hWnd,IDC_TIME,strTime); Sleep(1000); } } 该线程函数没有参数也不返回函数值。只要m_bRun为TRUE线程一直运行。 双击IDC_START按钮完成该按钮的消息函数 void CMultiThread1Dlg::OnStart() { // TODO: Add your control notification handler code here hThreadCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, NULL, 0, ThreadID); GetDlgItem(IDC_START)-EnableWindow(FALSE); GetDlgItem(IDC_STOP)-EnableWindow(TRUE); } 双击IDC_STOP按钮完成该按钮的消息函数 void CMultiThread1Dlg::OnStop() { // TODO: Add your control notification handler code here m_bRunFALSE; GetDlgItem(IDC_START)-EnableWindow(TRUE); GetDlgItem(IDC_STOP)-EnableWindow(FALSE); } 编译并运行该例程体会使用Win32 API编写的多线程。 例程2 MultiThread2 该线程演示了如何传送一个一个整型的参数到一个线程中以及如何等待一个线程完成处理。 建立一个基于对话框的工程MultiThread2在对话框IDD_MULTITHREAD2_DIALOG中加入一个编辑框和一个按钮ID分别是IDC_COUNTIDC_START 按钮控件的标题为“开始” 在MultiThread2Dlg.h文件中添加线程函数声明 void ThreadFunc(int integer); 注意线程函数的声明应在类CMultiThread2Dlg的外部。 在类CMultiThread2Dlg内部添加protected型变量: HANDLE hThread; DWORD ThreadID; 分别代表线程的句柄和ID。 打开ClassWizard为编辑框IDC_COUNT添加int型变量m_nCount。在MultiThread2Dlg.cpp文件中添加void ThreadFunc(int integer) { int i; for(i0;iinteger;i) { Beep(200,50); Sleep(1000); } } 双击IDC_START按钮完成该按钮的消息函数 void CMultiThread2Dlg::OnStart() { UpdateData(TRUE); int integerm_nCount; hThreadCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, (VOID*)integer, 0, ThreadID); GetDlgItem(IDC_START)-EnableWindow(FALSE); WaitForSingleObject(hThread,INFINITE); GetDlgItem(IDC_START)-EnableWindow(TRUE); } 顺便说一下WaitForSingleObject函数其函数原型为DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); hHandle为要监视的对象一般为同步对象也可以是线程的句柄 dwMilliseconds为hHandle对象所设置的超时值单位为毫秒 当在某一线程中调用该函数时线程暂时挂起系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内线程所等待 的对象变为有信号状态则该函数立即返回如果超时时间已经到达dwMilliseconds毫秒但hHandle所指向的对象还没有变成有信号状态函 数照样返回。参数dwMilliseconds有两个具有特殊意义的值0和INFINITE。若为0则该函数立即返回若为INFINITE则线程一直被挂起 直到hHandle所指向的对象变为有信号状态时为止。 本例程调用该函数的作用是按下IDC_START按钮后一直等到线程返回再恢复IDC_START按钮正常状态。编译运行该例程并细心体会。 例程3 MultiThread3 传送一个结构体给一个线程函数也是可能的可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体 typedef struct { int firstArgu, long secondArgu, … }myType,*pMyType; 创建线程时CreateThread(NULL,0,threadFunc,pMyType,…); 在threadFunc函数内部可以使用“强制转换” int intValue((pMyType)lpvoid)-firstArgu; long longValue((pMyType)lpvoid)-seconddArgu; …… 例程3 MultiThread3将演示如何传送一个指向结构体的指针参数。 建立一个基于对话框的工程MultiThread3在对话框IDD_MULTITHREAD3_DIALOG中加入一个编辑框IDC_MILLISECOND一个按钮IDC_START标题 为“开始” 一个进度条IDC_PROGRESS1 打开ClassWizard为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond为进度条IDC_PROGRESS1添加CProgressCtrl型变量 m_ctrlProgress 在MultiThread3Dlg.h文件中添加一个结构的定义 struct threadInfo { UINT nMilliSecond; CProgressCtrl* pctrlProgress; }; 线程函数的声明 UINT ThreadFunc(LPVOID lpParam); 注意二者应在类CMultiThread3Dlg的外部。 在类CMultiThread3Dlg内部添加protected型变量: HANDLE hThread; DWORD ThreadID; 分别代表线程的句柄和ID。 在MultiThread3Dlg.cpp文件中进行如下操作 定义公共变量 threadInfo Info 双击按钮IDC_START添加相应消息处理函数void CMultiThread3Dlg::OnStart() { // TODO: Add your control notification handler code here UpdateData(TRUE); Info.nMilliSecondm_nMilliSecond; Info.pctrlProgressm_ctrlProgress; hThreadCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, Info, 0, ThreadID); } 在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句 { …… // TODO: Add extra initialization here m_ctrlProgress.SetRange(0,99); m_nMilliSecond10; UpdateData(FALSE); return TRUE; // return TRUE unless you set the focus to a control } 添加线程处理函数UINT ThreadFunc(LPVOID lpParam) { threadInfo* pInfo(threadInfo*)lpParam; for(int i0;i100;i) { int nTemppInfo-nMilliSecond; pInfo-pctrlProgress-SetPos(i); Sleep(nTemp); } return 0; } 顺便补充一点如果你在void CMultiThread3Dlg::OnStart() 函数中添加语句编译运行你就会发现进度条不进行刷新主线程也停止了反应。什么原因呢这是因为WaitForSingleObject函数等待子线程 ThreadFunc结束时导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起任何消息都得不到处理而子线程ThreadFunc正 在设置进度条一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。这样两个线程都在互相等待死锁发生了编程时应注意避免 。 例程4 MultiThread4 该例程测试在Windows下最多可创建线程的数目。 建立一个基于对话框的工程MultiThread4在对话框IDD_MULTITHREAD4_DIALOG中加入一个按钮IDC_TEST和一个编辑框IDC_COUNT按钮标题为 “测试” 编辑框属性选中Read-only 在MultiThread4Dlg.cpp文件中进行如下操作 添加公共变量volatile BOOL m_bRunFlagTRUE; 该变量表示是否还能继续创建线程。 添加线程函数 DWORD WINAPI threadFunc(LPVOID threadNum) { while(m_bRunFlag) { Sleep(3000); } return 0; } 只要 m_bRunFlag 变量为TRUE线程一直运行。 双击按钮IDC_TEST添加其响应消息函数void CMultiThread4Dlg::OnTest() { DWORD threadID; GetDlgItem(IDC_TEST)-EnableWindow(FALSE); long nCount0; while(m_bRunFlag) { if(CreateThread(NULL,0,threadFunc,NULL,0,threadID)NULL) { m_bRunFlagFALSE; break; } else { nCount; } } //不断创建线程直到再不能创建为止 m_nCountnCount; UpdateData(FALSE); Sleep(5000); //延时5秒等待所有创建的线程结束 GetDlgItem(IDC_TEST)-EnableWindow(TRUE); m_bRunFlagTRUE; } 五、MFC对多线程编程的支持 MFC中有两类线程分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环而用户界面线程有自己的消息 队列和消息循环。 工作者线程没有消息机制通常用来执行后台计算和维护任务如冗长的计算过程打印机的后台打印等。用户界面线程一般用于处理独 立于其他线程执行之外的用户输入响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言这两种线程是没有区别的它们都 只需线程的启动地址即可启动线程来执行任务。 在MFC中一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行该函数有两种重载形式分别用于创建工作者线程和用户界 面线程。两种重载函数原型和参数分别说明如下 (1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, nPriorityTHREAD_PRIORITY_NORMAL, UINT nStackSize0, DWORD dwCreateFlags0, LPSECURITY_ATTRIBUTES lpSecurityAttrsNULL); PfnThreadProc:指向工作者线程的执行函数的指针线程函数原型必须声明如下 UINT ExecutingFunction(LPVOID pParam); 请注意ExecutingFunction()应返回一个UINT类型的值用以指明该函数结束的原因。一般情况下返回0表明执行成功。 pParam传递给线程函数的一个32位参数执行函数将用某种方式解释该值。它可以是数值或是指向一个结构的指针甚至可以被忽略 nPriority线程的优先级。如果为0则线程与其父线程具有相同的优先级 nStackSize:线程为自己分配堆栈的大小其单位为字节。如果nStackSize被设为0则线程的堆栈被设置成与父线程堆栈相同大小 dwCreateFlags如果为0则线程在创建后立刻开始执行。如果为CREATE_SUSPEND则线程在创建后立刻被挂起 lpSecurityAttrs线程的安全属性指针一般为NULL (2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass, int nPriorityTHREAD_PRIORITY_NORMAL, UINT nStackSize0, DWORD dwCreateFlags0, LPSECURITY_ATTRIBUTES lpSecurityAttrsNULL); pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针该导出类定义了被创建的用户界面线程的启动、退出等其它参数的意义同形式1。使用函数的这个原型 生成的线程也有消息机制在以后的例子中我们将发现同主线程的机制几乎一样。 下面我们对CWinThread类的数据成员及常用函数进行简要说明。 m_hThread当前线程的句柄 m_nThreadID:当前线程的ID m_pMainWnd指向应用程序主窗口的指针 BOOL CWinThread::CreateThread(DWORD dwCreateFlags0, UINT nStackSize0, LPSECURITY_ATTRIBUTES lpSecurityAttrsNULL); 该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义该函数执行成功返回 非0值否则返回0。 一般情况下调用AfxBeginThread()来一次性地创建并启动一个线程但是也可以通过两步法来创建线程首先创建CWinThread类的一个 对象然后调用该对象的成员函数CreateThread()来启动该线程。 virtual BOOL CWinThread::InitInstance(); 重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值否则返回0。用户界面线程经常重载该函数工作者线程一般不 使用InitInstance()。 virtual int CWinThread::ExitInstance(); 在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码0表示执行成功非0值用来标识各种错误。同 InitInstance()成员函数一样该函数也只适用于用户界面线程。 六、MFC多线程编程实例 在Visual C 6.0编程环境中我们既可以编写C风格的32位Win32应用程序也可以利用MFC类库编写C风格的应用程序二者各有其优缺点。基于Win32的 应用程序执行代码小巧运行效率高但要求程序员编写的代码较多且需要管理系统提供给程序的所有资源而基于MFC类库的应用程序可以 快速建立起应用程序类库为程序员提供了大量的封装类而且Developer Studio为程序员提供了一些工具来管理用户源程序其缺点是类库代码很庞大。由于使用类库所带来的快速、简捷和功能强大等优越性因此 除非有特殊的需要否则Visual C推荐使用MFC类库进行程序开发。 我们知道MFC中的线程分为两种用户界面线程和工作者线程。我们将分别举例说明。 用 MFC 类库编程实现工作者线程 例程5 MultiThread5 为了与Win32 API对照我们使用MFC 类库编程实现例程3 MultiThread3。 建立一个基于对话框的工程MultiThread5在对话框IDD_MULTITHREAD5_DIALOG中加入一个编辑框IDC_MILLISECOND一个按钮IDC_START标题 为“开始” 一个进度条IDC_PROGRESS1 打开ClassWizard为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond为进度条IDC_PROGRESS1添加CProgressCtrl型变量 m_ctrlProgress 在MultiThread5Dlg.h文件中添加一个结构的定义 struct threadInfo { UINT nMilliSecond; CProgressCtrl* pctrlProgress; }; 线程函数的声明UINT ThreadFunc(LPVOID lpParam); 注意二者应在类CMultiThread5Dlg的外部。 在类CMultiThread5Dlg内部添加protected型变量 CWinThread* pThread; 在MultiThread5Dlg.cpp文件中进行如下操作定义公共变量threadInfo Info; 双击按钮IDC_START添加相应消息处理函数 void CMultiThread5Dlg::OnStart() { // TODO: Add your control notification handler code here UpdateData(TRUE); Info.nMilliSecondm_nMilliSecond; Info.pctrlProgressm_ctrlProgress; pThreadAfxBeginThread(ThreadFunc, Info); } 在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句 { …… // TODO: Add extra initialization here m_ctrlProgress.SetRange(0,99); m_nMilliSecond10; UpdateData(FALSE); return TRUE; // return TRUE unless you set the focus to a control } 添加线程处理函数 UINT ThreadFunc(LPVOID lpParam) { threadInfo* pInfo(threadInfo*)lpParam; for(int i0;i100;i) { int nTemppInfo-nMilliSecond; pInfo-pctrlProgress-SetPos(i); Sleep(nTemp); } return 0; } 用 MFC 类库编程实现用户界面线程 创建用户界面线程的步骤 使用ClassWizard创建类CWinThread的派生类以CUIThread类为例 class CUIThread : public CWinThread { DECLARE_DYNCREATE(CUIThread) protected: CUIThread(); // protected constructor used by dynamic creation // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CUIThread) public: virtual BOOL InitInstance(); virtual int ExitInstance(); //}}AFX_VIRTUAL // Implementation protected: virtual ~CUIThread(); // Generated message map functions //{{AFX_MSG(CUIThread) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG DECLARE_MESSAGE_MAP() }; 重载函数InitInstance()和ExitInstance()。 BOOL CUIThread::InitInstance() { CFrameWnd* wndnew CFrameWnd; wnd-Create(NULL,UI Thread Window); wnd-ShowWindow(SW_SHOW); wnd-UpdateWindow(); m_pMainWndwnd; return TRUE; } 创建新的用户界面线程 void CUIThreadDlg::OnButton1() { CUIThread* pThreadnew CUIThread(); pThread-CreateThread(); } 请注意以下两点 A、在UIThreadDlg.cpp的开头加入语句 #include UIThread.h B、把UIThread.h中类CUIThread()的构造函数的特性由 protected 改为 public。 用户界面线程的执行次序与应用程序主线程相同首先调用用户界面线程类的InitInstance()函数如果返回TRUE继续调用线程的Run() 函数该函数的作用是运行一个标准的消息循环并且当收到WM_QUIT消息后中断在消息循环过程中Run()函数检测到线程空闲时没有消 息也将调用OnIdle()函数最后Run()函数返回MFC调用ExitInstance()函数清理资源。 你可以创建一个没有界面而有消息循环的线程例如你可以从CWinThread派生一个新类在InitInstance函数中完成某项任务并返回 FALSE这表示仅执行InitInstance函数中的任务而不执行消息循环你可以通过这种方法完成一个工作者线程的功能。 例程6 MultiThread6 建立一个基于对话框的工程MultiThread6在对话框IDD_MULTITHREAD6_DIALOG中加入一个按钮IDC_UI_THREAD标题为“用户界面线程” 右击工程并选中“New Class…”为工程添加基类为CWinThread派生线程类CUIThread。 给工程添加新对话框IDD_UITHREADDLG标题为“线程对话框”。 为对话框IDD_UITHREADDLG创建一个基于CDialog的类CUIThreadDlg。使用ClassWizard为CUIThreadDlg类添加WM_LBUTTONDOWN消息的处理函数 OnLButtonDown如下 void CUIThreadDlg::OnLButtonDown(UINT nFlags, CPoint point) { AfxMessageBox(You Clicked The Left Button!); CDialog::OnLButtonDown(nFlags, point); } 在UIThread.h中添加 #include UIThreadDlg.h 并在CUIThread类中添加protected变量CUIThread m_dlg class CUIThread : public CWinThread { DECLARE_DYNCREATE(CUIThread) protected: CUIThread(); // protected constructor used by dynamic creation // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CUIThread) public: virtual BOOL InitInstance(); virtual int ExitInstance(); //}}AFX_VIRTUAL // Implementation protected: CUIThreadDlg m_dlg; virtual ~CUIThread(); // Generated message map functions //{{AFX_MSG(CUIThread) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG DECLARE_MESSAGE_MAP() }; 分别重载InitInstance()函数和ExitInstance()函数 BOOL CUIThread::InitInstance() { m_dlg.Create(IDD_UITHREADDLG); m_dlg.ShowWindow(SW_SHOW); m_pMainWndm_dlg; return TRUE; } int CUIThread::ExitInstance() { m_dlg.DestroyWindow(); return CWinThread::ExitInstance(); } 双击按钮IDC_UI_THREAD添加消息响应函数 void CMultiThread6Dlg::OnUiThread() { CWinThread *pThreadAfxBeginThread(RUNTIME_CLASS(CUIThread)); } 并在MultiThread6Dlg.cpp的开头添加 #include UIThread.h 好了编译并运行程序吧。每单击一次“用户界面线程”按钮都会弹出一个线程对话框在任何一个线程对话框内按下鼠标左键都会 弹出一个消息框。 七、线程间通讯 一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程 和次要线程间要进行通信。这种线程间的通信不但是难以避免的而且在多线程编程中也是复杂和频繁的下面将进行说明。 使用全局变量进行通信 由于属于同一个进程的各个线程共享操作系统分配该进程的资源故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局 变量我们建议使用volatile 修饰符它告诉编译器无需对该变量作任何的优化即无需将它放到一个寄存器中并且该值可被外部改变。如果线程间所需传递的信息较复 杂我们可以定义一个结构通过传递指向该结构的指针进行传递信息。 使用自定义消息 我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现 的。利用Windows操作系统的消息驱动机制当一个线程发出一条消息时操作系统首先接收到该消息然后把该消息转发给目标线程接收消 息的线程必须已经建立了消息循环。 例程7 MultiThread7 该例程演示了如何使用自定义消息进行线程间通信。首先主线程向CCalculateThread线程发送消息WM_CALCULATECCalculateThread线 程收到消息后进行计算再向主线程发送WM_DISPLAY消息主线程收到该消息后显示计算结果。 建立一个基于对话框的工程MultiThread7在对话框IDD_MULTITHREAD7_DIALOG中加入三个单选按钮IDC_RADIO1IDC_RADIO2IDC_RADIO3标 题分别为1234......101234......501234......100。加入按钮IDC_SUM标题为“求和”。加入标签框IDC_STATUS属性 选中“边框” 在MultiThread7Dlg.h中定义如下变量 protected: int nAddend; 代表加数的大小。 分别双击三个单选按钮添加消息响应函数void CMultiThread7Dlg::OnRadio1() { nAddend10; } void CMultiThread7Dlg::OnRadio2() { nAddend50; } void CMultiThread7Dlg::OnRadio3() { nAddend100; } 并在OnInitDialog函数中完成相应的初始化工作 BOOL CMultiThread7Dlg::OnInitDialog() { …… ((CButton*)GetDlgItem(IDC_RADIO1))-SetCheck(TRUE); nAddend10; …… 在MultiThread7Dlg.h中添加 #include CalculateThread.h #define WM_DISPLAY WM_USER2 class CMultiThread7Dlg : public CDialog { // Construction public: CMultiThread7Dlg(CWnd* pParent NULL); // standard constructor CCalculateThread* m_pCalculateThread; …… protected: int nAddend; LRESULT OnDisplay(WPARAM wParam,LPARAM lParam); …… 在MultiThread7Dlg.cpp中添加 BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog) …… ON_MESSAGE(WM_DISPLAY,OnDisplay) END_MESSAGE_MAP() LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam) { int nTemp(int)wParam; SetDlgItemInt(IDC_STATUS,nTemp,FALSE); return 0; } 以上代码使得主线程类CMultiThread7Dlg可以处理WM_DISPLAY消息即在IDC_STATUS标签框中显示计算结果。 双击按钮IDC_SUM添加消息响应函数 void CMultiThread7Dlg::OnSum() { m_pCalculateThread (CCalculateThread*)AfxBeginThread(RUNTIME_CLASS(CCalculateThread)); Sleep(500); m_pCalculateThread-PostThreadMessage(WM_CALCULATE,nAddend,NULL); } OnSum()函数的作用是建立CalculateThread线程延时给该线程发送WM_CALCULATE消息。 右击工程并选中“New Class…”为工程添加基类为 CWinThread 派生线程类 CCalculateThread。 在文件CalculateThread.h 中添加 #define WM_CALCULATE WM_USER1 class CCalculateThread : public CWinThread { …… protected: afx_msg LONG OnCalculate(UINT wParam,LONG lParam); …… 在文件CalculateThread.cpp中添加 LONG CCalculateThread::OnCalculate(UINT wParam,LONG lParam) { int nTmpt0; for(int i0;i(int)wParam;i) { nTmptnTmpti; } Sleep(500); ::PostMessage((HWND)(GetMainWnd()-GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL); return 0; } BEGIN_MESSAGE_MAP(CCalculateThread, CWinThread) //{{AFX_MSG_MAP(CCalculateThread) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP ON_THREAD_MESSAGE(WM_CALCULATE,OnCalculate) //和主线程对比注意它们的区别 END_MESSAGE_MAP() 在CalculateThread.cpp文件的开头添加一条 #include MultiThread7Dlg.h 以上代码为 CCalculateThread 类添加了 WM_CALCULATE 消息消息的响应函数是 OnCalculate其功能是根据参数 wParam 的值进行累加累加结果在临时变量nTmpt中延时0.5秒向主线程发送WM_DISPLAY消息进行显示nTmpt作为参数传递。 编译并运行该例程,体会如何在线程间传递消息。 八、线程的同步 虽然多线程能给我们带来好处但是也有不少问题需要解决。例如对于像磁盘驱动器这样独占性系统资源由于线程可以执行进程的任 何代码段且线程的运行是由系统调度自动完成的具有一定的不确定性因此就有可能出现两个线程同时对磁盘驱动器进行操作从而出现 操作错误又例如对于银行系统的计算机来说可能使用一个线程来更新其用户数据库而用另外一个线程来读取数据库以响应储户的需要 极有可能读数据库的线程读取的是未完全更新的数据库因为可能在读的时候只有一部分数据被更新过。 使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象下面我们只介绍最常用的四种 临界区CCriticalSection 事件CEvent 互斥量CMutex 信号量CSemaphore 通过这些类我们可以比较容易地做到线程同步。 A、使用 CCriticalSection 类 当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象拥有临界区的线程可以访 问被保护起来的资源或代码段其他希望进入临界区的线程将被挂起等待直到拥有临界区的线程放弃临界区时为止这样就保证了不会在同 一时刻出现多个线程访问共享资源。 CCriticalSection类的用法非常简单步骤如下 定义CCriticalSection类的一个全局对象以使各个线程均能访问如CCriticalSection critical_section 在访问需要保护的资源或代码之前调用CCriticalSection类的成员Lock获得临界区对象 critical_section.Lock(); 在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象则调用Lock()的线程获得临界区否则线程 将被挂起并放入到一个系统队列中等待直到当前拥有临界区的线程释放了临界区时为止。 访问临界区完毕后使用CCriticalSection的成员函数Unlock()来释放临界区critical_section.Unlock(); 再通俗一点讲就是线程A执行到critical_section.Lock();语句时如果其它线程(B)正在执行critical_section.Lock();语句后且 critical_section. Unlock();语句前的语句时线程A就会等待直到线程B执行完critical_section. Unlock();语句线程A才会继续执行。 下面再通过一个实例进行演示说明。 例程8 MultiThread8 建立一个基于对话框的工程MultiThread8在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件两个按钮的ID分别为 IDC_WRITEW和IDC_WRITED标题分别为“写‘W’”和“写‘D’”两个编辑框的ID分别为IDC_W和IDC_D属性都选中Read-only 在MultiThread8Dlg.h文件中声明两个线程函数 UINT WriteW(LPVOID pParam); UINT WriteD(LPVOID pParam); 使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD 在MultiThread8Dlg.cpp文件中添加如下内容 为了文件中能够正确使用同步类在文件开头添加#include afxmt.h 定义临界区和一个字符数组为了能够在不同线程间使用定义为全局变量CCriticalSection critical_section; char g_Array[10]; 添加线程函数UINT WriteW(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); critical_section.Lock(); //锁定临界区其它线程遇到critical_section.Lock();语句时要等待 //直至执行critical_section.Unlock();语句 for(int i0;i10;i) { g_Array[i]W; pEdit-SetWindowText(g_Array); Sleep(1000); } critical_section.Unlock(); return 0; } UINT WriteD(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); critical_section.Lock(); //锁定临界区其它线程遇到critical_section.Lock();语句时要等待 //直至执行critical_section.Unlock();语句 for(int i0;i10;i) { g_Array[i]D; pEdit-SetWindowText(g_Array); Sleep(1000); } critical_section.Unlock(); return 0; } 分别双击按钮IDC_WRITEW和IDC_WRITED添加其响应函数 void CMultiThread8Dlg::OnWritew() { CWinThread *pWriteWAfxBeginThread(WriteW, m_ctrlW, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteW-ResumeThread(); } void CMultiThread8Dlg::OnWrited() { CWinThread *pWriteDAfxBeginThread(WriteD, m_ctrlD, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteD-ResumeThread(); } 由于代码较简单不再详述。编译、运行该例程您可以连续点击两个按钮观察体会临界类的作用。 B、使用 CEvent 类 CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时唤醒另外一个线程的同步对象。例如在某些网络应用程序中一个线程 记为A负责监听通讯端口另外一个线程记为B负责更新用户数据。通过使用CEvent 类线程A可以通知线程B何时更新用户数据。每一个CEvent 对象可以有两种状态有信号状态和无信号状态。线程监视位于其中的CEvent 类对象的状态并在相应的时候采取相应的操作。 在MFC中CEvent 类对象有两种类型人工事件和自动事件。一个自动CEvent 对象在被至少一个线程释放后会自动返回到无信号状态而人工事件对象获得信号后释放可利用线程但直到调用成员函数ReSetEvent()才 将其设置为无信号状态。在创建CEvent 类的对象时默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下 1、CEvent(BOOL bInitiallyOwnFALSE, BOOL bManualResetFALSE, LPCTSTR lpszNameNULL, LPSECURITY_ATTRIBUTES lpsaAttributeNULL); bInitiallyOwn:指定事件对象初始化状态TRUE为有信号FALSE为无信号 bManualReset指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件FALSE为自动事件 后两个参数一般设为NULL在此不作过多说明。 2、BOOL CEventSetEvent(); 将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件则 CEvent 类对象保持为有信号状态直到调用成员函数ResetEvent ()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件则在SetEvent()将事件设置为有信号状态后CEvent 类对象由系统自动重置为无信号状态。 如果该函数执行成功则返回非零值否则返回零。 3、BOOL CEventResetEvent(); 该函数将事件的状态设置为无信号状态并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置故自动事件不需 要调用该函数。如果该函数执行成功返回非零值否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经 介绍了该函数。由于语言描述的原因CEvent 类的理解确实有些难度但您只要通过仔细回味下面例程多看几遍就可理解。 例程9 MultiThread9 建立一个基于对话框的工程MultiThread9在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件按钮的ID为IDC_WRITEW标 题为“写‘W’”两个编辑框的ID分别为IDC_W和IDC_D属性都选中Read-only; 在MultiThread9Dlg.h文件中声明两个线程函数 UINT WriteW(LPVOID pParam); UINT WriteD(LPVOID pParam); 使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD 在MultiThread9Dlg.cpp文件中添加如下内容 为了文件中能够正确使用同步类在文件开头添加 #include afxmt.h 定义事件对象和一个字符数组为了能够在不同线程间使用定义为全局变量。 CEvent eventWriteD; char g_Array[10]; 添加线程函数 UINT WriteW(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); for(int i0;i10;i) { g_Array[i]W; pEdit-SetWindowText(g_Array); Sleep(1000); } eventWriteD.SetEvent(); return 0; } UINT WriteD(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); WaitForSingleObject(eventWriteD.m_hObject,INFINITE); for(int i0;i10;i) { g_Array[i]D; pEdit-SetWindowText(g_Array); Sleep(1000); } return 0; } 仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待直到事件eventWriteD为有信号该线程才往下执行因为eventWriteD对象 是自动事件则当WaitForSingleObject()返回时系统自动把eventWriteD对象重置为无信号状态。 双击按钮IDC_WRITEW添加其响应函数 void CMultiThread9Dlg::OnWritew() { CWinThread *pWriteWAfxBeginThread(WriteW, m_ctrlW, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteW-ResumeThread(); CWinThread *pWriteDAfxBeginThread(WriteD, m_ctrlD, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteD-ResumeThread(); } 编译并运行程序单击“写‘W’”按钮体会事件对象的作用。 C、使用CMutex 类 互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用 。当然互斥对象也可以用于同一进程的各个线程间但是在这种情况下使用临界区会更节省系统资源更有效率。 D、使用CSemaphore 类 当需要一个计数器来限制可以使用某个线程的数目时可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零则所有 对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时计 数值减1一个线程完成了对被控共享资源的访问时计数值增1。这个被CSemaphore 类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。 CSemaphore 类的构造函数原型及参数说明如下 CSemaphore (LONG lInitialCount1, LONG lMaxCount1, LPCTSTR pstrNameNULL, LPSECURITY_ATTRIBUTES lpsaAttributesNULL); lInitialCount:信号量对象的初始计数值即可访问线程数目的初始值 lMaxCount信号量对象计数值的最大值该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目 后两个参数在同一进程中使用一般为NULL不作过多讨论 在用CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数每增 加一个线程对共享资源的访问当前可用资源计数就会减1只要当前可用资源计数是大于0的就可以发出信号量信号。但是当前可用计数减 小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目不能再允许其它线程的进入此时的信号量信号将无法发出。线程在处 理完共享资源后应在离开的同时通过ReleaseSemaphore()函数将当前可用资源数加1。 下面给出一个简单实例来说明 CSemaphore 类的用法。 例程10 MultiThread10 建立一个基于对话框的工程MultiThread10在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件按钮的ID为IDC_START 标题为“同时写‘A’、‘B’、‘C’”三个编辑框的ID分别为IDC_A、IDC_B和IDC_C属性都选中Read-only 在MultiThread10Dlg.h文件中声明两个线程函数 UINT WriteA(LPVOID pParam); UINT WriteB(LPVOID pParam); UINT WriteC(LPVOID pParam); 使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC 在MultiThread10Dlg.cpp文件中添加如下内容 为了文件中能够正确使用同步类在文件开头添加 #include afxmt.h 定义信号量对象和一个字符数组为了能够在不同线程间使用定义为全局变量CSemaphore semaphoreWrite(2,2); //资源最多访问线程2个当前可访问线程数2个 char g_Array[10]; 添加三个线程函数 UINT WriteA(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); CString str; for(int i0;i10;i) { pEdit-GetWindowText(str); g_Array[i]A; strstrg_Array[i]; pEdit-SetWindowText(str); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0; } UINT WriteB(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); CString str; for(int i0;i10;i) { pEdit-GetWindowText(str); g_Array[i]B; strstrg_Array[i]; pEdit-SetWindowText(str); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0; } UINT WriteC(LPVOID pParam) { CEdit *pEdit(CEdit*)pParam; pEdit-SetWindowText(); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); for(int i0;i10;i) { g_Array[i]C; pEdit-SetWindowText(g_Array); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0; } 这三个线程函数不再多说。在信号量对象有信号的状态下线程执行到WaitForSingleObject语句处继续执行同时可用线程数减1若线程执 行到WaitForSingleObject语句时信号量对象无信号线程就在这里等待直到信号量对象有信号线程才往下执行。 双击按钮IDC_START添加其响应函数 void CMultiThread10Dlg::OnStart() { CWinThread *pWriteAAfxBeginThread(WriteA, m_ctrlA, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteA-ResumeThread(); CWinThread *pWriteBAfxBeginThread(WriteB, m_ctrlB, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteB-ResumeThread(); CWinThread *pWriteCAfxBeginThread(WriteC, m_ctrlC, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteC-ResumeThread(); } 好吧多线程编程就介绍到这里希望本文能对您有所帮助。转载于:https://www.cnblogs.com/lanye/p/3316560.html