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

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

编辑:说三道四文库 发布时间:2017-06-26 06:36
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. 
 
这篇文章好,多谢楼上的朋友!
真的很好
谢谢!
像证券一样交易云资源,Zimory一年内收入翻两番并获德国证交所青睐 移动SDK崛起:未来会不会整合成一大坨? 关于 VS2010 异常捕获 在线编程教育平台Treehouse推免费iPad应用 微软发布Windows 8.1企业预览版 功能更强劲 如何管理和优化日益增长的代码复杂度? SDCC 2013大会讲师名单议题更新 8月1日前3.8折优惠截止 IBM Rational首席技术官:DevOps是一门哲学 Mozilla联手黑莓推进Web安全技术,推出开源测试平台Minion 苹果如何培养SOHO一族高效工作 再造Ruby:CryENGINE 3.5游戏引擎特性详解 Eclipse基金会涉足物联网,M2M标准是否已获东风? SDN部署前必须考虑的7个事项 【云先锋 40】初创公司DataStax:专注于Cassandra,三年融资8370万美元 内容创作工具的革新:Facebook前CTO推文字处理应用Quip 微软为Android手机推出Office 仅面向Office 365用户开放使用 编程、创业、开源感悟——SDCC 2013讲师云风专访 原生广告:移动推广的热门新宠 隐私之战:金山手机毒霸宣布开放安全检测平台 世界人民的头上有张监控大网——Xkeyscore 美国“监控门”情报收集系统 X-Keyscore 技术揭秘 为啥REST如此重要? 并非PHP Frameworks而是WordPress让PHP更加流行! Twitter Bootstrap深受开发者喜爱的11大理由 假如3D打印机也有“撤销”键 大数据的游戏运营:不能起死回生,只能锦上添花 15个步骤创立技术公司,并收获千万用户(一) 红帽再发力 将MongoDB整合到Linux系统 ZestFinance:前谷歌CIO创立的机器学习+大数据分析公司 7月份浏览器份额:IE仍最受欢迎 Chrome成最大赢家 2013 Q2 Android手机出货量再创新高 iPhone三年来新低 备份日志语句! 98的MSDOS程序都不能运行了,说与系统连接的设备不能运行了,怎么解决?急!!! 问个色彩寄存器的问题 关于WINDOWS服务定时运行的问题,求救!!!!。。 加急!如何只将文档发送给[代办事宜]中,而在邮件数据库中不留痕迹? initialize the Borland Database Engine (error $2501) 带小数点的数字用什么变量类型? 谁用过DHTMLEDIT控件?帮帮忙介绍一下用法。 该死的ACCESS 怎样编写服务器接受客户端的相应程序 我的机子安装完WIN2000后,访问其它的计算机就死机,别的机子访问我机子的时候,我的机子也会死机。 怎样合并两个表??????? 一个有关ISampleGrabber接口调用的问题!Please HELP ME... 如何访问客户端指定路径上的ACCESS数据库? 可以远程安装win2k服务器版么?(各位帮忙!) 还有1。2的考试吗 一条sql语句! 我能在w2k下为非PnP串口设备写驱动吗? 哪位可以解释一下,构造函数到底有什么作用??? 难道没有高手帮我解决这个问题吗?mysql 版主请进 菜单如何显示在工具栏之上? 有关线程之间交换信息的问题 为什么在WIDNOWS98下用MASM50编译的汇编程序不能运行? sql如何自动定时清空日志?(在线等待!) 游戏 有没有办法用纯Javascript取得<form>里面用post方法post过来的数据?100分在线等候!!!绝对给分!!!!! 大家好,级联菜单怎么做呀?再线等待!!!急 邮件发送不了??? 一个落后的问题,用DAO能链接sql server数据库吗?如果可以,怎么链接!? PB中用代码建立ODBC数据源问题。 response中的问题 大家好。 我是新人。。。。。。。 关于用vc操作word中表格的问题 我的机子运行了几个用bde连接数据库的程序,在delphi里就打开不了src了出现以下错误! 求大家一个问题.我已经快疯了?我在等待ing 请问如何用delphi编写一个录音的com组件,在ASP中调用,详细些! 不知道自己到底喜不喜欢女朋友,纳闷.......... 急问!!! 很简单的问题,但是很急,解决了就给分!! 根据一些来历不明的消息说,微软将在2008年停止对 VB.NET 进行技术支持。是真的吗??? 怎样实现倒退和快进,用mediaplay组件播放影片,想在影片结束时自动调用某个过程,这个影片结束的事件是什么? 怪:DataGrid中选择一行后此行不能编辑! 有谁用过工商银行的国际借记卡了吗? 如何将反馈表中的若干表单内容发送到指定的邮箱并同时抄送到另一信箱? 使用游标时出现 -1002 的错误如何解决? 数据的初始化问题。 [求助] 如何用DirectX显示YUV格式的图像 我在安装Red Hat8时说一个文件坏了,但我有源代码,怎样编译成成哪个文件。 桌面内容不见了! 怎么获得当前目录 Nobody can finish the work in two days.(同义句转换) —— ——can finish the work in two days. 如何输入英文的句号?就是带方块状的,我做片头需要用到,可是输入的一直都是实心的小圆点,我需要的是方状的 during which 和among which 的用法有什么区别?最好有例句 it is the first time for him to make a speech in the presence of many people这个句子有没有语法错误 do you need help at the old people's () show also home tell ,usic 中选并用适当形式 Let me ( ) (sit) on the bench( )里填写什么? Time is money in a way.in a way 的意思是 A.sometimes B.partly C.actually D.on the way ()the money you can help the people who need help填那个介词,为什么:at,for,by,with What____you______at eight yesterday evening?A.have,done B.did,do C.were,doing D.will,do the first time i ever witnessed the death of .求翻译,谢谢.the first time i ever witnessed the death of sea creature from bullet wounds was on the beach in my hometown a neighborhood boy ,Zach ,fell over a wounded seal while walking in the sea. Do you need some people ____ (help) you ____(finish) the plan? 要解释解释 We (plan)to go to the zoo ,but on the way ,it (begin)to rain I'm sorry to have kept you waiting .-- Oh .not at all .I ___ here for only a few minutes.本题要用现在完成时还是过去完成时?为什么(我认为应该用过去完成时但不确定) Some _______________(Germany) students will visit our university next month.说一下德国人这个词的词性转化,顺便说一下其他国家的转换 There is nothing to do but ______ till he comes back. A:wait B:to wait C:waited D:waiting --i am sorry to have kept you waiting long ---never mind .i (-----)here for only a few minutesa have been b have come c have arrived d waited The leaders of a Canadian c____ will come to visit our university next week. I thumped my fist against the wall behind my back and choked back my sobs. ―I’ m sorry to have kept you waiting .―Never mind.I ( )(be) here for pnly a few minutes. If you really have to leave during the meeting,you'd better leave_____the back door.A.for;B.by;C.across;D.out 答案给的是B,是leave by为一个整体,还是by the back door为一个整体【就是怎么断句啊 /疑问】为什么其他几个选 two days ____enough for me to finish the work .I need a third day.A is not B is C are not Dare 选择什么?为什么 i'm sorry to have kept you waiting long.never mind.I( )here for only a fewminutes.A、have been B、have come C、have arrived D、waited if you really have to leave during the meeting,you'd better leave __the back door.为什么填by,不填out Two days isn't enough for me to finish the work .I need --- day.A.a third .b.the third .C.one third . I borrowed some books from Mike.中的from Mike是什么成分,如果是状语,是什么状语 那可以把some book Do you have to write to your friend?-Sorry,but I really ______. A.can't B.must C.have D.should 27.Two days are not enough for me to finish the work.I need___day.A.other B.the other C.the third D.third to share some thoughts 在句中是状语,还是定语呀?1、Tonight, with a thankful heart, I have asked for a final opportunity to share some thoughts on the journey we have traveled together and the future of our nation."to share some thoughts Two days is not enough for me to finish the work,I need ____ day.A.a third B.a few C.some D.the third请详细分析所选答案的原因,并分析考察的知识点, 连词成句you,I,need,to,sports,help,with,my I borrowed some books from Mike.中的from Mike是什么成分,如果是状语,是什么状语那可以把some book 理解为直接宾语 而Mike理解为间接宾语 因为lend sth to sb 就说sth 是直接宾语 sb是间接宾语 Two days is not enough for him to finish the work.He needs _______ day.选择一项:a.the thirdb.otherc.a thirdd.the other为什么选C啊 英语need,sports,help,we,with,you,to怎么组成句子 My cousin usually walks to school every morning 同义句 Two days is not enough for him to finish the work.He needs _______ day.A、 other  B、 the other  C、 the third  D、 a third  They stayed with me for two weeks,_______ they drank all the beer i had .A.which B.which time C.during which time D.during which为什么不选D.是不是during 连接表时间的定语从句就是这种情况下后面只能用which time.The Second W John usually goes to school () bike 1.give sb some advice on sth意思是?和give sb some advice 区别是?2.用give sb some advice on sth 造个句子. ping-pong,does,mother,play,your(?)连词成句 写出下列句子的同义句1.the pands are interesting 2.there are many people here on vacation.3.the room has three doors.4.everyone is having fun. could sb give me some advice?just write a simple sentence about your self or your habit.eg:I play baskball.harry up! Does your father play ping-pong everyday?这句哪儿错了 Alot of English have three names (急)give sb some advice作文怎么写给病人的建意 用ask sb about sth造句、 I took a bus to school yesterday.改为否定句I ( )( )a bus to school yesterday. give sb.some advice 用中文怎么说 Does your brother play soccer?(作否定回答) he__(take)no.3 bus to school yesterday 怎么填 是took还是tooks Sandy usually walks to school in the morningkuickly! Does your brother play sports every day?为什么在yourDoes your brother play sports every day?为什么在your brother前面要does,your brother是第三人称单数? 选词填空 took,expensive,because,invited,yesterday,winter,surprise,decided,favorite,schoolthe( )vacation started( )at Su Hai and Su Yang's( ) Su Hai and Su yang's family ( )to eat out.their good friend,Mike ( )them to his( )restaurant.It w ______it took you to finish the work!A.what much time B.what a lot of timeC.how long time D.how lots of time ask sb about sth造句只要2个就好 jack took a bus to school yesterday改为同义语 It took me two days to finish the work.对"tow days"提问. of which和during which的区别 I need two days to finish the work.空格中填上many的正确形式 如何将英文输入法设置为主输入法我不小心把英文输入法删掉了 现在只能ABC是主输入法 我随后又添加了英文输入法 怎么将英文输入法调成主输入法 只能ABC有点不方便 跪求定语从句中in which和during which的区别和用法,
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘