说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

哪个高手可以详细讲一下进程和线程吗?多谢

编辑:说三道四文库 发布时间:2017-08-24 11:15
HTML文档下载 WORD文档下载 PDF文档下载
一个程序可以看作一个进程,而一个进程又可以分为多个线程
進程:一個正在運行的程序的實例,包括所有可執行模塊或dll的代碼、數據等。
線程:系統中的基本運行單位,包括線程內核對象和線程堆棧。
你可以把進程看成是一個容器,含有所有的資源.而線程就是使用進程中資源在進程的范圍中運行的基本調度單位。由它來完成所有的操作。
一个程序启动后系统核心模块会创建一个进程,同时自动创建一个主线程来运行代码,
CPU是以线程为执行单元来实现多任务的
线程即"轻进程",在现代操作系统中可以作为调度的基本单位.
进程可以作为调度的基本单位,也同时是资源分配的基本单位,多个线程共享一个进程的内存空间.
Visual C++ 5.0中多线程编程技术  
北京大学计算机科学技术研究所 潘爱民  

--------------------------------------------------------------------------------
 
【摘要】本文首先介绍了Windows的多线程技术与用途,然后讲述了利用Visual C++ 5.0进行多线程编程的过程,重点描述了MFC类库对多线程应用编程的方法和实现,最后用几个例子对多线程编程方法作了分析。
关键字:Win32 API,进程,线程,多线程,同步对象,Visual C++,MFC

1. 引言

Windows系统平台经历了16位到32位的转变后,系统运行方式和任务管理方式有了很大的变化,在Windows 95和Windows NT中,每个Win32程序在独立的进程空间上运行,32位地址空间使我们从16位段式结构的64K段限制中摆脱出来,逻辑上达到了4G的线性地址空间,我们在设计程序时,不再需要考虑编译的段模式,同时还提高了大程序的运行效率。独立进程空间的另一个更大的优越性是大大提高了系统的稳定性,一个应用的异常错误不会影响其它的应用,这对于现在的桌面环境尤为重要。
在Windows的一个进程内,包含一个或多个线程,线程是指进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源,包括打开的文件、信号标识及动态分配的内存等等。一个进程内的所有线程使用同一个32位地址空间,而这些线程的执行由系统调度程序控制,调度程序决定那个线程可执行和什么时候执行线程,线程有优先级别,优先权较低的线程必须等到优先权较高的线程执行完任务后再执行。在多处理器的机器上,调度程序可以把多个线程放到不同的处理器上运行,这样可以使处理器的任务平衡,也提高系统的运行效率。
32位Windows环境下的Win32 API提供了多线程应用开发所需要的接口函数,但Win16和Win32s对多线程应用并不支持,利用Visual C++ 5.0中提供的标准C库也可以开发多线程应用程序,而相应的MFC4.21类库则封装了多线程编程的类,因而用户在开发时可根据应用的需要和特点选择相应的工具。
如果用户的应用需要有多个任务同时进行相应的处理,那么使用多线程是很理想的选择,如网络文件服务功能的应用,若采用单线程编程方法,需要循环检查网络的连接、磁盘驱动器的状况,并在适当的时候显示这些数据,必须等到一遍查询后才能刷新数据的显示,对使用者来说,延迟可能很长;而一个多线程的应用可以把这些任务分给多个线程,一个线程可检查网络,另一个线程管理磁盘驱动器,还有一个线程负责显示数据,三个线程结合起来共同完成文件服务的功能,使用者也可以及时看到网络的变化。多线程应用范围很广,尤其现在的桌面平台上,系统的许多功能如网络(Internet)、打印、字处理、图形图象、动画和文件管理都在一个系统下运行,更需要我们的应用能同时处理多个事件,而这些正是多线程所可以实现的。本文讲述了利用Visual C++ 5.0进行多线程开发的编程技术。

2. 基于Visual C++的多线程编程


Visual C++ 5.0提供了Windows应用程序的集成开发环境Deveoper Studio,在这个环境里,用户既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,两者各有优点,基于Win32的应用程序执行代码小巧,运行效率高,但要求程序员书写代码多,且需要管理所有系统提供给程序的资源;而基于MFC类库的应用程序可以快速建立起应用,类库为程序员提供了大量的封装类,而且Deveoper Studio为程序员提供了一些工具管理用户源程序,但缺点是类库代码很庞大,应用执行代码离不开这些代码,由于使用类库所带来的快速、简捷和功能强大等优越性,因而,除非是特殊的需要,Visual C++提倡使用MFC类库进行应用开发。
多线程的编程在Win32方式下和MFC类库支持下的原理是一致的,进程的主线程在任何需要的时候都可以创建新的线程,当线程执行完任务后,自动终止线程,当进程结束后,所有的线程都终止。所有活动的线程共享进程的资源,所以在编程时,需要考虑在多个线程访问同一资源时,产生冲突的问题,当一个线程正在访问一个进程对象,这时另一个线程要改变该对象,这时可能会产生错误的结果,所以程序员在编程时要解决这种冲突。
下面讲述在Win32 基础上进行多线程编程的过程。


2.1 用Win32函数创建和终止线程


Win32函数库里提供了多线程控制的操作函数,包括创建线程、终止线程、建立互斥区等。首先,在应用程序的主线程或者其它活动线程的适当的地方创建新的线程,创建线程的函数如下:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, 
DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, 
LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); 
参数lpThreadAttributes 指定了线程的安全属性,在Windows 95中被忽略;
参数dwStackSize 指定了线程的堆栈深度;
参数lpStartAddress 指定了线程的起始地址,一般情况为下面原型的函数
DWORD WINAPI ThreadFunc( LPVOID ); 
参数 lpParameter指定了线程执行时传送给线程的32位参数,即上面函数的参数;
参数dwCreationFlags指定了线程创建的特性;
参数 lpThreadId 指向一个DWORD变量,可返回线程ID值。
如果创建成功则返回线程的句柄,否则返回NULL。

创建了新的线程后,线程开始启动执行,如果在dwCreationFlags中用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这过程中可以调用函数
BOOL SetThreadPriority( HANDLE hThread, int nPriority); 
设置线程的优先权。
当线程的函数返回后,线程自动终止,如果要想在线程的执行过程中终止的话,可以调用函数
VOID ExitThread( DWORD dwExitCode); 
如果在线程的外面终止线程的话,可以用下面的函数
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode ); 
但注意,该函数可能会引起系统不稳定,而且,线程所占用的资源也不释放,因此,一般情况下,建议不要使用该函数。
如果要终止的线程是进程内的最后一个线程的话,在线程被终止后,相应的进程也终止。


2.2 用Win32函数控制线程对共享资源的访问


在线程体内,如果该线程完全独立,与其它的线程没有数据存取等资源操作上的冲突的话,可以按照通常单线程的方法进行编程,但在多线程处理时,情况常常不是这样,线程之间经常要同时去访问一些资源,例如,一个线程负责公式计算,另一个线程负责结果的显示,两个线程都要访问同一个结果变量。这时如果不进行冲突控制的话,很有可能会显示不正确的结果。
对共享资源进行访问引起冲突是不可避免的,但我们可以用以下的办法进行操作控制:
(1) 通过设置线程的互斥体对象,在可能冲突的地方进行同步控制;
先建立互斥体对象,得到句柄;
HANDLE CreateMutex( ); 
然后在线程可能冲突区域的开始,即访问共享资源之前,调用WaitForSingleObject,把句柄传给函数,请求占用互斥体对象
dwWaitResult = WaitForSingleObject(hMutex, 5000L); 
共享资源访问完后,释放对互斥体对象的占用
ReleaseMutex(hMutex);
互斥体对象在同一时刻只能被一个线程占用,在一个线程占用时,若有另一线程想占用的话,必须等前一线程释放才能成功。

(2) 设置信号,在操作共享资源前,打开信号,完成操作后,关闭信号;
类似于互斥体对象的处理,首先创建信号对象
HANDLE CreateSemaphore( );
或者打开一个信号对象
HANDLE OpenSemaphore( );
然后在线程的访问共享资源之前,调用WaitForSingleObject,
共享资源访问完后,释放对信号对象的占用
ReleaseSemaphore();
信号对象允许同时多个线程共享资源的访问,在创建对象时指定最大可同时访问线程数,当一个线程申请访问成功后,信号对象中的计数器减一,调用ReleaseSemaphore函数后,信号对象中的计数器加一。其中计数器值大于等于0,小于等于创建时指定的最大值。利用信号对象,我们不仅可以控制共享资源的访问,还可以在应用的初始化时候使用,假定一个应用在创建一个信号对象时,设置其计数器的初始值为0,这样就阻塞了其它线程,保护了资源,待初始化完成后,调用ReleaseSemaphore函数增加其计数器至最大值,进行正常的存取访问。

(3) 利用事件对象的状态,进行线程对共享资源的访问。
用ResetEvent函数设置事件对象状态不允许线程通过;
用SetEvent函数设置事件对象状态可以允许线程通过。
事件分为手工释放和自动释放,如果是手工释放,则按照上两函数处理事件的状态;如果是自动释放,则在一个线程结束后,自动清除事件状态,允许其它线程通过。

(4) 设置排斥区,在排斥区异步执行,它只能在同一进程的线程之间共享资源处理,虽然这时上面三种方法也可以用,但使用排斥区的方法使同步管理的效率更高;
先定义一个CRITICAL_SECTION结构的排斥区对象;
在进程使用之前,先对对象作初始化,调用下面函数:
VOID InitializeCriticalSection( LPCRITICAL_SECTION );
当一个线程使用排斥区时,调用函数
EnterCriticalSection或者TryEnterCriticalSection
要求占用,退出排斥区时 ,调用
LeaveCriticalSection
释放对排斥区对象的占用,供其它线程使用。

互斥体对象、信号对象和事件对象,也可以用于进程间的线程同步操作。在用Win32函数创建了对象时,我们可以指定对象名字,可以设置同步对象在子进程的继承性,创建返回得到的是HANDLE句柄,我们可以用函数DuplicateHandle来复制对象句柄,这样每个进程都可以拥有同一对象的句柄,实现进程之间的线程同步操作。另外,在同一进程内,我们可以用OpenMutext、OpenSemaphore和OpenEvent获得指定名字的同步对象的句柄。
排斥区异步执行的线程同步方法只能用于同一进程的线程之间共享资源处理,但是这种方法的使用效率很高,而且编程也相对简单一些。
从上面介绍的几种方法来看,一般我们利用Win32函数进行线程同步的基本编程模式如下:

初始化:创建同步对象
........其它工作..........
线程模块:
等待对象:调用WaitForSingleObject或者MsgWaitForMultipleObjects
注意一定要判断函数的返回值,根据不同的情况作相应的处理。
............ 对共享资源的访问...........
释放同步对象的控制

在Visual C++中,除了利用Win32函数进行多线程同步控制外,如果我们用到了MFC类库,则可以利用已经封装成C++类结构的同步对象,可以使我们的编程更加简捷。

3. 基于MFC的多线程编程


在Visual C++ 5.0随带的MFC 4.21类库中,也提供了多线程编程的支持,基本的原理与上面所讲的基于Win32函数的设计一致,但由于MFC对同步对象作了封装,因此对用户编程实现来说,更加方便,避免了对象句柄管理上的繁琐工作。更重要的是,在多个窗口线程情况下,MFC中直接提供了用户接口线程的设计。
在MFC中,线程分为两种:用户接口线程和辅助线程。用户接口线程常用于接收用户的输入,处理相应的事件和消息。在用户接口线程中,包含一个消息处理循环,其中CWinApp就是一个典型的例子,它从CWinThread派生,负责处理用户输入产生的事件和消息。辅助线程常用于任务处理,比如计算,它不要求用户输入,对用户而言,它在后台运行。Win32 API并不区分这两种线程类型,它只是获取线程的起始地址,然后开始执行线程。而MFC则针对不同的用户需要,作了分类。如果我们需要编写多个有用户接口的线程的应用,利用Win32 API要写很多的框架代码来完成每个线程的消息事件的处理,而用MFC则可以充分发挥MFC中类的强大功能,还可以使用ClassWizard来帮助我们管理类的消息映射和成员变量等,我们可以把精力集中到应用相关的代码编写上。
辅助线程编程较为简单,设计的思路与上节所讲的基本一致,一个基本函数代表了一个线程,经创建线程并启动线程后,线程进入运行状态。如果线程用到共享资源,则需要进行资源同步处理。共享资源的同步处理在两种线程模式下完全一致。
我们知道,基于MFC的应用程序有一个应用对象,它是CWinApp派生类的对象,该对象代表了应用进程的主线程,当线程执行完(通常是接收到WM_QUIT消息)并退出线程时,由于进程没有其它的线程存在,故进程也自动结束。类CWinApp从CWinThread派生,CWinThread是用户接口线程的基本类,我们在编写用户接口线程时,需要从CWinThread派生我们自己的线程类,ClassWizard可以帮助我们完成这个工作。


下面列出编写用户接口线程的基本步骤:


1. 用ClassWizard派生一个新的类,设置基类为CWinThread。
注意,类的DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE宏是必须的,因为创建线程时,需要动态创建类的对象。根据需要可以把初始化和结束代码分别放到类的InitInstance和ExitInstance函数中。如果需要创建窗口的话,可以在InitInstance函数中完成。

2. 创建线程并启动线程。
可以用两种方法建立用户接口线程。
(1) MFC提供了两个版本的AfxBeginThread函数,其中一个用于创建用户接口线程,函数原型如下:
CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass, int nPriority, 
UINT nStackSize , DWORD dwCreateFlags, 
LPSECURITY_ATTRIBUTES lpSecurityAttrs );
参数pThreadClass指定线程的运行类,函数返回线程对象。
我们在创建线程时,可以指定线程先挂起,把参数dwCreateFlags设置为CREATE_SUSPENDED,然后做一些初试工作,变量赋值等,最后再调用线程类的ResumeThread函数,启动线程。
函数AfxBeginThread的另一个版本指定一个线程函数,并设置相应的参数,其它设置及用法与上述函数基本相同。
(2) 我们也可以不用AfxBeginThread创建线程,而是分两步完成:
a. 先调用线程类的构造函数创建一个线程对象;
b. 然后调用CWinThread::CreateThread函数创建该线程。
注意这种情况下,在线程类中需要有公有的构造函数以创建其相应的C++对象。
线程建立并启动后,线程在线程函数执行过程中一直有效,如果是线程对象,则在对象被删除之前,先结束线程,CWinThread已经为我们完成了线程结束的工作。

3. 同步对象的使用。不管是辅助线程,还是用户接口线程,在存取共享资源时,都需要保护共享资源,以免引起冲突造成错误。处理方法类似于Win32 API函数的使用,但MFC为我们提供了几个同步对象C++类:
CSyncObject,CMutex,CSemaphore,CEvent,CCriticalSection
这里CSyncObject为其它四个类的基类,后四个类分别对应了前面所讲的四个Win32 API同步对象。
通常我们在C++对象的成员函数中使用共享资源,或者把共享资源封装在C++类的内部,我们可以把线程同步操作封装在对象类的实现函数中,这样在应用中的线程使用C++对象时,可以象一般对象一样使用,简化了使用部分代码的编写,这正是面向对象编程的思想。这样编写的类我们称为线程安全类。在设计线程安全类时,根据具体情况,我们在类中加入一个同步对象类数据成员。然后在类的成员函数中,凡是所有修改公共数据或者读取公共数据的地方都要加进相应的同步调用。一般的处理是,创建一个CSingleLock或者CMultiLock对象,然后调用其Lock函数,当对象结束时,自动在析构函数中调用Unlock函数,当然我们也可以在任何希望的地方调用Unlock函数。
如果我们不是在特定的C++对象中使用共享资源,而是在特定的函数中使用共享资源(这样的函数称为线程安全函数),那么我们还是按照前面介绍的办法,先建立同步对象,然后调用等待函数,直到可以访问资源,最后释放对同步对象的控制。
下面我们讨论四个同步对象分别适用的场合:
1. 如果某个线程必须等待某些事件发生才能存取相应资源的话,那么我们就用CEvent。
2. 如果一个应用同时可以有多个线程存取相应资源的话,那么我们就用CSemaphore。
3. 如果有多个应用(多个进程)同时存取相应资源的话,那么我们就用CMutext,否则就用CCriticalSection。
使用线程安全类或者线程安全函数进行编程,比不考虑线程安全的编程要复杂,尤其在进行调试时,情况更为复杂,我们必须灵活使用Visual C++提供的调试工具,针对具体的问题,以保证共享资源的安全存取。线程安全编程的的另一缺点是运行效率相对要低,即使在单个线程运行的情况也会损失一些效率。所以我们在实际工作中,应对具体问题具体分析,以选择合适的编程方法。

4. 多线程编程例程序分析
上面讲述了在Visual C++ 5.0中进行多线程编程的技术要点,为了充分说明这种技术,我们分析了Visual C++提供的有关多线程的例程序,看看一些多线程元素的典型用法。读者可运行这些例程序,以获得多线程运行的直观效果。

(1) MtRecalc
例程序MtRecalc的功能是在一个视窗里完成简单的加法运算,用户可输入加数和被加数,程序完成两数相加。用户可通过菜单选择单线程或用辅助线程来做加法运算。如果选择辅助线程进行加法运算,则在进行运算的过程中,用户可继续做一些界面操作,如访问菜单,编辑数值等,甚至可终止辅助运算线程。为了使效果更加明显,程序在计算中使用了循环和延时,模拟一个复杂的计算过程。
在程序中的CRecalcDoc类中,用到了一个线程对象和四个同步事件对象:

CWinThread* m_pRecalcWorkerThread;
HANDLE m_hEventStartRecalc;
HANDLE m_hEventRecalcDone;
HANDLE m_hEventKillRecalcThread;
HANDLE m_hEventRecalcThreadKilled;

当用户选择了菜单项Worker Thread后,多线程功能才有效,这时,或者选择菜单项Recalculate Now,或者在视窗中的编辑控制转移焦点时,都会调用函数
void CRecalcDoc::UpdateInt1AndInt2(int n1, int n2, BOOL bForceRecalc);
在多线程的情况下,又会调用下面的CRecalcDoc::RecalcInSecondThread函数:

void CRecalcDoc::RecalcInSecondThread()
{
if (m_pRecalcWorkerThread == NULL)
{
m_pRecalcWorkerThread =
AfxBeginThread(RecalcThreadProc, &m_recalcThreadInfo);
}

m_recalcThreadInfo.m_nInt1 = m_nInt1;
m_recalcThreadInfo.m_nInt2 = m_nInt2;
POSITION pos = GetFirstViewPosition();
CView* pView = GetNextView(pos);
m_recalcThreadInfo.m_hwndNotifyRecalcDone = pView->m_hWnd;
m_recalcThreadInfo.m_hwndNotifyProgress = AfxGetMainWnd()->m_hWnd;
m_recalcThreadInfo.m_nRecalcSpeedSeconds = m_nRecalcSpeedSeconds;

SetEvent(m_hEventRecalcDone);
ResetEvent(m_hEventKillRecalcThread);
ResetEvent(m_hEventRecalcThreadKilled);
SetEvent(m_hEventStartRecalc);
}

上面加粗的语句为与多线程直接有关的代码,应用调用AfxBeginThread启动了线程,把m_recalcThreadInfo作为参数传给线程函数。函数中最后的四行语句设置了四个事件对象的状态,这些事件对象在文档类的构造函数中创建。下面是实际的运算线程函数:

UINT RecalcThreadProc(LPVOID pParam)
{
CRecalcThreadInfo* pRecalcInfo = (CRecalcThreadInfo*)pParam;

BOOL bRecalcCompleted;
while (TRUE)
{
bRecalcCompleted = FALSE;
if (WaitForSingleObject(pRecalcInfo->m_hEventStartRecalc, INFINITE)
!= WAIT_OBJECT_0)
break;
if (WaitForSingleObject(pRecalcInfo->m_hEventKillRecalcThread, 0)
== WAIT_OBJECT_0)
break; // Terminate this thread by existing the proc.

ResetEvent(pRecalcInfo->m_hEventRecalcDone);

bRecalcCompleted = SlowAdd(pRecalcInfo->m_nInt1,
pRecalcInfo->m_nInt2,
pRecalcInfo->m_nSum,
pRecalcInfo,
pRecalcInfo->m_nRecalcSpeedSeconds,
pRecalcInfo->m_hwndNotifyProgress);

SetEvent(pRecalcInfo->m_hEventRecalcDone);

if (!bRecalcCompleted) // If interrupted by kill then...
break; // terminate this thread by exiting the proc.

::PostMessage(pRecalcInfo->m_hwndNotifyRecalcDone,
WM_USER_RECALC_DONE, 0, 0);
}

if (!bRecalcCompleted)
SetEvent(pRecalcInfo->m_hEventRecalcThreadKilled);

return 0;
}

BOOL SlowAdd(int nInt1, int nInt2, int& nResult, CRecalcThreadInfo* pInfo, int nSeconds,
HWND hwndNotifyProgress)
{
CWnd* pWndNotifyProgress = CWnd::FromHandle(hwndNotifyProgress);

BOOL bRestartCalculation = TRUE;
while (bRestartCalculation)
{
bRestartCalculation = FALSE;

for (int nCount = 1; nCount < 20; nCount++)
{
if (pInfo != NULL
&& WaitForSingleObject(pInfo->m_hEventKillRecalcThread, 0) 
== WAIT_OBJECT_0)
{
if (hwndNotifyProgress != NULL)
{
pWndNotifyProgress->PostMessage(
WM_USER_RECALC_IN_PROGRESS);
}
return FALSE; // Terminate this recalculation
}

if (pInfo != NULL
&&WaitForSingleObject(pInfo->m_hEventStartRecalc, 0) 
== WAIT_OBJECT_0)
{
nInt1 = pInfo->m_nInt1;
nInt2 = pInfo->m_nInt2;
bRestartCalculation = TRUE;
continue;
}
// update the progress indicator
Sleep(nSeconds * 50);
}
// update the progress indicator
}

nResult = nInt1 + nInt2;
return TRUE;
}

上面的代码充分显示了几个事件对象的用法,当线程刚启动时,先等待m_hEventStartRecalc的状态为允许,然后检查m_hEventKillRecalcThread事件对象的状态,注意这两个等待函数调用的第二个参数的区别。在进入计算函数之前,设置m_hEventRecalcDone事件为不允许状态,待计算结束后,将其设置为允许状态。在计算函数的处理过程中,循环检查事件m_hEventKillRecalcThread和m_hEventStartRecalc的状态,如果m_hEventKillRecalcThread事件允许,则退出线程,终止计算。
当计算线程在计算时,主线程可继续接受用户输入,包括菜单选择,用户可通过菜单项终止掉计算线程,终止线程的处理比较简单,代码如下:

void CRecalcDoc::OnKillWorkerThread()
{
SetEvent(m_hEventKillRecalcThread);
SetEvent(m_hEventStartRecalc);

WaitForSingleObject(m_hEventRecalcThreadKilled, INFINITE);

m_pRecalcWorkerThread = NULL;
m_bRecalcInProgress = FALSE; // but m_bRecalcNeeded is still TRUE
UpdateAllViews(NULL, UPDATE_HINT_SUM);
}

通过设置m_hEventKillRecalcThread事件对象,计算线程的循环就会检测到该事件的状态,最终引起线程的退出。注意,线程的终止由函数的退出自然终止,而没有用强行办法终止,这样可保证系统的安全性。另外,在程序的很多地方使用了PostMessage来更新计算进度的指示,使用PostMessage函数发送消息可立即返回,不需等待,避免阻塞,比较符合多线程编程的思想,建议读者使用这种消息发送方法。尤其是在多个UI线程编程时,更显重要。

(2) Mtmdi
例程序MtMDI是一个MDI应用,每一个子窗口是一个用户接口线程,子窗口里有一个来回弹跳的小球,小球的运动由计时器控制,我们不加讨论。这里我们看看UI线程的创建过程,及它与MDI的结合。
通过菜单命令New Bounce,在主框架窗口类里响应菜单命令,函数如下:
void CMainFrame::OnBounce()
{
CBounceMDIChildWnd *pBounceMDIChildWnd = new CBounceMDIChildWnd;
if (!pBounceMDIChildWnd->Create( _T("Bounce"),
WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, rectDefault, this))
return;
}

函数调用子框架窗口的创建函数,如下:

BOOL CBounceMDIChildWnd::Create(LPCTSTR szTitle, LONG style,
const RECT& rect, CMDIFrameWnd* parent)
{
// Setup the shared menu
if (menu.m_hMenu == NULL)
menu.LoadMenu(IDR_BOUNCE);
m_hMenuShared = menu.m_hMenu;

if (!CMDIChildWnd::Create(NULL, szTitle, style, rect, parent))
return FALSE;

CBounceThread* pBounceThread = new CBounceThread(m_hWnd);
pBounceThread->CreateThread();

return TRUE;
}

当CBounceMDIChildWnd子窗口被删除时,Windows会同时删除CBounceWnd窗口(内嵌在线程对象pBounceThread里),因为它是CBounceMDIChildWnd的子窗口。由于CBounceWnd运行在单独的线程里,当CBounceWnd子窗口被删除时,CWinThread线程对象也会自动被删除。
上述函数生成一个新的UI线程对象pBounceThread,并调用CreateThread函数创建线程,至此线程已经创建,但还需要做初始化工作,如下函数InitInstance所示:

int CBounceThread::InitInstance()
{
CWnd* pParent = CWnd::FromHandle(m_hwndParent);
CRect rect;
pParent->GetClientRect(&rect);

BOOL bReturn = m_wndBounce.Create(_T("BounceMTChildWnd"),
WS_CHILD | WS_VISIBLE, rect, pParent);

if (bReturn)
m_pMainWnd = &m_wndBounce;

return bReturn;
}

注意这里把m_pMainWnd设置为新创建的CBounceWnd窗口是必需的,只有这样设置了,才能保证当CBounceWnd窗口被删除时,线程才会自动被删除。

(3) Mutexes
例程序Mutexes是一个对话框程序,除了主线程外,还有两个线程,一个用于计数,一个用于显示,该例子中两个线程都是从CWinThread派生,但并不用于消息循环处理,派生类重载了Run函数,用于完成其计数和显示的任务。
在对话框类里使用了一个内嵌的CMutex对象,对话框初始化时,创建两个线程,并设置相应的参数,然后启动运行两个线程。
当用户设置了对话框的同步检查框标记后,两个线程的同步处理有效。在计数线程的循环里,先调用CSingleLock::Lock函数,然后进行计数修改,最后调用CSingleLock::Unlock函数,注意这里的CSingleLock对象根据主对话框的CMutex对象产生;在显示线程的循环里,先调用CSingleLock::Lock函数,然后取到计数值,最后调用CSingleLock::Unlock函数,注意这里的CSingleLock对象也是由主对话框的CMutex对象产生。类似这种情况,一个线程要读取数据,另一个线程要修改数据,是我们处理多线程问题最典型的情况。这里所采用的方法也是具有典型意义的。源代码可查看例程序或通过联机帮助获取。

5. 结束语
本文较为详细地介绍了在Microsoft Windows 95和Microsoft Windows NT环境下,利用Visual C++进行多线程编程的技术,讨论了Win32 API中对多线程的支持,介绍了线程的创建和终止过程,详细讨论了各种同步对象的含义和使用方法。文章还介绍了利用MFC 4.21对多线程的支持,讨论了两种线程模型的使用方法和同步对象的使用。最后,我们对三个例程序中有关多线程的内容进行了分析,说明了一些多线程编程的典型处理方法。
多线程函数是Win32中不同于Win16的一个重要方面,其编程技术也较为新颖,在程序设计思路上不同于传统的模块结构化方法,比一般的面向对象的思路也较为复杂,尤其对于多处理器平台的处理更为复杂。要设计出性能良好的多线程程序,不仅需要对操作系统的处理过程很清楚,还需要对具体应用有一个全面的认识,并对应用中各线程部分的关系非常清楚,对同步模块中的同步对象的具体含义应尽可能清晰明了,以利于在程序中控制同步事件的发生,避免出现死锁或不能同步处理的现象。
在其它的开发语言如Visual Basic(5.0)中也提供了多线程的支持,但从性能和安全的角度考虑,这种多线程支持非常受限制。不过对于一般的应用,用这种处理方法已经足够了。
目前大多数的计算机都是单处理器(CPU)的,在这种机器上上运行多线程程序,有时反而会降低系统性能,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,但对于大部分时间被阻塞的线程(例如等待文件 I/O 操作),可以用一个单独的线程来完成,这样可把CPU时间让出来,使程序获得更好的性能。因此,在设计多线程应用时,需要慎重选择,具体情况具体处理,以使应用程序获得最佳的性能。

参考资料:
1. Multithreading with C and Win32, Microsoft Developer Network - Library, April 1997.
2. Multithreading with C++ and MFC, Microsoft Developer Network - Library, April 1997.
3. Visual C++ Programmer's Guide, Microsoft Press, 1997.
4. Visual C++ Samples, Microsoft Developer Network - Library, April 1997.
5. Microsoft Foundation Class Reference, Microsoft Developer Network - Library, Oct. 1997. 
 
这篇文章好,多谢楼上的朋友!
真的很好
谢谢!
Spring Framework 4.0M1发布,支持JDK 8、Java EE 7 “渣打科营编程马拉松赛”正式启动 官网同时上线 Android应该支持OpenCL的十大理由 传谷歌考虑和Facebook竞购众包地图Waze 谷歌工程师再次公布Windows漏洞 并称微软很难合作 Glassdoor评前景最令员工乐观的十大科技公司 华为上榜 下一代Android深度前瞻 第18级API功能揭秘 Web开发者不容错过的20段CSS代码 8大核心要点:我们能用HTML5 Canvas做什么? 移动周报:直接拿来用!最火的Android开源项目 前MySQL CEO:云将重新定义开发者的每一件事 CIO必知六件事,至少必须想到的! PPT集萃(四):来自五位中外专家分享的虚拟化和云存储实践 Jolla和Sailfish OS,那些你应该知道的事儿 不仅保值还能升值:过时的苹果电脑创下67.1万美元售价 三十天完成跨平台办公套件Joeffice的开发 jQuery 1.10.0和2.0.1同时发布 软件初创公司招投的九个必备技巧 BYOD浪潮即将来袭 六大年轻杀手重塑企业软件 灵活运用AppFlood:提高APP eCPM的10个技巧 事半功倍:你应该知道的HTML5五大特性 创建API监视器的五大技巧 流量激增15倍+ 乐蜂网桃花节大促背后的技术事 Windows 8和IE 10:如何保障视频播放? 云计算大会讲师秀之13:梁家恩,12年积累,构建永久免费的语音云平台 众望所归:智能手机芯片将为服务器所用 没有学位,他通过以下四步进入Google 谈谈Google Polymer以及Web UI框架的未来 保密入髓:前苹果员工揭示公司是如此善于保守秘密 调查显示:软件开发公司出现“人才荒” 三星拟6月20日在伦敦召开新品发布会 推手机新品Galaxy和ATIV 求助《《关于图标的问题》》 PB连接不上SYBASE!急!在线! 在任何时间任何地点,都不要找任何理由不学英语 初学者问题50分:关于操作符* 关于import的问题 关于asa的问题 一个小问题,请求帮助!!!!!!!! 求救!请教关于__cdecl的问题 近日开发一足球博彩性质网站,请各位推荐几个漂亮的前台页面参考参考,非常感谢! 有关WebService的问题,请高手指教!!!Up有分 我装上apache_2.0.43后,输入http://localhost:端口/不能运行(有人说是端口冲突,我改成别的端口还是不行),还有什么地方容易出错,请高手明 关于File的一个问题:如何基于已经存在的文件生成File对象? 郁闷呀!这两段程序有什么不一样的?送分 win98上两台机同时“装Internet连接工享”,却出现不允许,自动关掉了共享. 救命啊!!!!!!!!!!!!!!!!!!!!!!!!!!!11111 哪里能下在受信任的根证书? 高分相赠;控件问题,想实现既可以选择又可以输入的控件;如何做谢谢; 获取当前系统日期的函数是什么? 怎样写权限设置,通过用户权限控制菜单??? 救命!!!!!!!!!!!!!!!11 谁做过OA的安装盘?有没有办法用第三方工具配置OA系统? 一个简单的错误,请各位大哥帮助! 进入WIN98蓝屏,请高手赐教,谢谢 紧急求助,如何编写文本使得程序能读取网络上另一台机上的表 关于session的问题 如何用VC对麦克风编程哪? 为什么我的win2000的web服务器老是莫名其妙的停掉呢? 各位老大!能不能用DEPHI写一个程序创建NOTES表单???? 怎样把程序做成曲线图的样子? COMBOBOX查找问题? 很菜的,关于窗体的问题 怎么给jbuiler添加oracle驱动程序!!!!! 谁有手写签名和痕迹保存留的例子???能给我一个吗???一定重谢!! 哪有for win的gd库下载?谢谢 我得代码有错误吗!!在线紧急等待 如何用Java访问微软的DDE Server? 在windows xp中怎么建立虚拟服务器呀? 解决问题,马上结贴 xp風格的問題 请问如何让一个表格的边框的宽度和表格内部的线的宽度不一样? 请问wsad和eclipse哪里有下呢? 如何理解下面的程序段 调查:你(单位)愿意花钱买服务端控件吗? 为什么有些网页是繁体字! 我终于知道什么是英雄了! 关于快捷键。 一个丑人的自白(爆笑) 转载 如何实现信息反馈? 怎样使DataGrid固定显示8行??? 一个简单的问题,关于CListCtrl.. C++文件流的怪问题,竟然不能再次打开??? 郁闷啊,男生到底是什么动物?`````能说说现在18~20岁男生的共同点吗?就是在对待女生上面.他们喜欢什么样的女生,或讨厌什么样的女生? 有一笔钱,如果单买甲种物品可以买150件;如果单买乙种物品可以买90件;现用这笔钱买了甲乙两种物品共100件.问甲乙两种物品各买了多少件?用一元一次方程解~设. 蜘蛛肚子为什么会有丝拜托各位了 3Q 男人究竟是什么动物? 臭味的小虫子怎样除掉?有时候桌子上时不时跑出来一种小虫子,类似蚂蚁,但很小,背上还有点亮亮的类似深灰色的背部.目前不知道咬人不咬人,它是什么虫子啊?怎样去除或者避免它出现?这种 请问这是啥蜘蛛?腹部是红色的 狗的生命有多长? 【急】方翅网蝽如何防治(就是那种身体黑色,翅膀白色的虫子,刚刚百度知道的)家里的衣服,窗户上都是这种虫.用过杀虫剂可是,杀完这一批,下一批又接着飞来.该怎么办啊 穿山甲的功效与作用 课堂冲浪数学P17九年级(全一册)的第七题用长20M的篱笆,一面靠墙围成一个长方形的园子,怎么围才能使园子的面积最大?最大面积是多少?解和设最好要写上.各位兄弟姐妹一定要帮帮偶啊!看解 鱼化石中 作者从鱼化石身上得到什么启示 穿山甲的甲片有什么用 狗的生命是几年 月亮与萤火虫的优点和缺点 谁知道:穿山甲粉的具体作用与功效? 课堂冲浪英语答案 七上l need it 读了月亮和萤火虫心里有什么心里话 穿山甲的壳有什么功效与作用 狗的生命有几年 小华在看一件商品价格时没有注意到小数点,读成六千零四远,其实原来的小数读出来只读一个零.原来小叔是多少? 用什么词形容蟋蟀的劳动 狗的生命有多少年? 突然想起来一个事 懂昆虫的朋友关注下大约是在91年那个样夏天 手里拿了个网捉蜻蜓 蜻蜓没捉几个主要是少 在草上看到了个像蜻蜓但比蜻蜓小些眼睛也没那么大 翅膀是竖的 趴在草上 蜘蛛织网和蜜蜂采蜜是同一种行为吗?答案中是这么说的,可蜘蛛织网不是攻击行为吗?蜜蜂采蜜不是觅食行为吗?是不是答案错了. 狗的生命是多少年 【昆虫学者们来帮忙】蠼螋寿命一只蠼螋一般寿命有多长?(我要略微靠谱的回答呦~) 求由抛物线y=x^2-1,直线x=2,y=0所围成的图形的面积三分之八,还是三分之四. 有4堆外表上一样的球,每堆4个.已知其中三堆是正品、一堆是次品,正品球每个重10克,次品球每个重11克,请你只称一次把次品找出来.怎么称? 学英语,三天打鱼两天晒网,一时觉得心血来潮,一时又觉得荡然无趣,怎么办? 求由抛物线y=x^2-1; 直线y=0,x=2,x=0所围成的图形的面积 62式军用望远镜谁知道什么牌子的质量好啊? 想学英语.但我没有上过学.虽然没上过学.但一般的字都认识,只是写不好. 可以用什么词来形容蟋蟀的劳动 62式军用望远镜哪里有,有质量好的没? 无法学英语 唐伯虎--《桃花庵》以前有看过一部电视剧是讲唐伯虎的.其中主题曲开始就是他的桃花庵歌:桃花坞里桃花庵,桃花庵下桃花仙; 桃花仙人种桃树,又摘桃花卖酒钱.酒醒只在花前坐,酒醉还来花 与百分数有关的成语或名言 在人工授粉时 为什么要对接受花粉的花朵进行去雄 唐伯虎的《桃花庵记》谁会 百分数有关的成语 我看见你的回答了,能具体点么就是植物的选择花卉这类有什么具体的植物(没花粉或者花粉少的)望指教! 长方体的体积是1620 立方厘米,长宽高都是自然数,他们的棱长和至少是多少厘米? 寻找古诗.该诗前两句是“一上一上又一上,一上上到高山上” 感染性钉螺是血吸虫病流行的重要传染源对吗 一个长方体的体积是1620立方厘米,它的长宽高均为自然数,它的棱长之和最少是( )厘米. 六个和百分数有关的成语 用什么词形容蜘蛛 棱长和是64厘米,且棱长都是自然数的长方体中,体积最大是多少立方厘米 萤火虫有哪两个特点? 在昆虫记里头. 与百分数有关的成语? 概括语言特点的用词 昆虫记萤火虫是什么动物,猎物是什么 用什么化学物质可以防紫外线的照射?什么物质+什么物质可以防御紫外线的照射?或验钞器光先的照射? 下面花卉中,哪些观赏的不是真正的花瓣A.三角梅(叶子花) B.玫瑰 .C.玉兰 A和B是正比例,B和C是正比例,问:A和C?1、正比例2、反比例3 、不成比例要有理由的! 蚂蝗寄生在人体内怎么办 求课文蝈蝈就叫蝈蝈,不要什么绿蝈蝈还有什么的,是鄂教版2009年的课文,2010年就换了 男人到底是什么样的动物男人的性格. 有一笔钱,单买甲商品可以买150件;单买乙商品可以买90件.用这笔钱买两种物品共100件.问各买几件?(用方程解) 蜘蛛肚子里有多丝啊,它是怎么制作的.呵呵,很好奇.所以问问,呵 美国“天翼掠过休斯敦”航空展揭幕韩独岛宣传片涉嫌非法引用日媒资料被要禁化武组织称叙利亚提交销毁化武初步计俄罗斯航空专家开始在美国上空进行观察麦当劳与四十年“老伙伴”亨氏番茄酱分美国共和党参议员计划推迟叶伦的美联储美国广播公司高管就“杀光中国人”视频日借太平洋岛国会议插手南海朴槿惠为职棒比赛开球 露甜美微笑【组纽约华裔家庭五人被杀案疑犯被捕 为被女教师被调离因婆婆抗拆 评论吁查幕后中国海警编队10月28日在中国钓鱼岛安倍称不容中国“武力崛起” 外媒称中美医保网站高管被爆系奥巴马夫人同学 马尼拉市长将赴港道歉 评论称菲律宾一哈萨克斯坦时装周:黑白搭配永不过时美海军航母编队在中国南海训练演习奥巴马夫妇携女儿赴教堂做礼拜 一家服奥巴马医保网站问题频出 美卫生部长被法国前高官称经济危机下的恐慌导致排外约有40万杭州人的骨头不够硬苹果今日发布OS X 10.11第四为什么中国人活得没意思?梦幻神域黑剑士怎么样 技能属性详解虐到哭:当你喜欢某一个人的时候8月5日手游开测表:《公主别闹了》精中国制造的现实与误区节奏幅度需注意,平衡线上的“涨停基因梦幻神域影精灵怎么样 奥义技能详解辞职创业的媒体人凭啥风生水起?动视暴雪15Q2收入7.59亿美元 互联网或成物流业救星小白谈狼人 刀塔传奇花式吊打狼人队讲进入基因行业的“门票”有多贵?DataEye:重度玩家占比30% 如何进入心仪的行业?山西原平四中领导多年校外补课 教育局70年之后,房子到底还是不是你的Roguelike玩法 《无尽地牢》股市小白,如何做到年盈利50%的?为防隔壁老王而工作,挣钱只为做亲子鉴孔教神童
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘