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

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

编辑:说三道四文库 发布时间:2017-03-24 12:33
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. 
 
这篇文章好,多谢楼上的朋友!
真的很好
谢谢!
在Delphi中使用IP控件 在程序中动态取得Win95/98网络邻居中工作组及计算机名-Delphi资料 在程序中获取网络资源信息-Delphi资料 自制THyperLink组件-Delphi资料 报表制作技巧(三)-Delphi资料 Delphi数据库开发及统计表格设计 Delphi文本和图形的打印方法 Delphi应用程序中中国式报表的制作 Windows环境下实时列表输出的实现方法-Delphi资料 报表制作技巧(二)-Delphi资料 Delphi编制的程序运行中动态制作报表 报表制作技巧(一)-Delphi资料 如何在WINDOWS中控制打印字体的长宽,而不受限于SIZE 的限制-Delphi资料 也谈为Delphi中数据库报表加网格 用Delphi4的QReport部件生成报表 用Delphi编写打印程序的窍门 用Word做Delphi报表输出工具 在DELPHI 文 本 和 图 形 的 打 印 在Delphi中运用QuickReport实现多栏列印 在打印或打印预览前如何获取报表页数-Delphi资料 自定义快速报表的打印预览窗口-Delphi资料 Delphi 3 图形VCL中Bug修改方案 Delphi hint的字体和色彩如何改变 Delphi3.0中复活节彩蛋及实现 Delphi数据库应用程序开发中图像数据的存取技巧 DELPHI图形编辑技巧二则 DELPHI显示JPEG图像文件 Delphi中CANVAS使用三例 Delphi中的图形显示技巧 DELPHI中画布应用技巧 Delphi中控件深度投影及阴影效果的实现 蚂蚁下载 关于弹出窗口的问题 串口通讯的问题 关于_RecordsetPtr的一个问题,百思不得其解,请求帮助。 文档中心的帖子怎么收藏? 怎么在java中向一个sql语句传参数,就像oracle的proc一样啊? 如何有一个人成分6000行的文本文件,请问如何预览后打印出来. 其他应用程序能调用notes中的代理吗 ?在我的表中设了一字段PKID,此字段是主键并且自增,当我插入一条记录时,我如何取得此记录的PKID(Hothehe) 这个帖子怎么打不开? 急需解 请问高手,设计一个分时系统要注意些什么? 哪有c++builder6的帮助和列子 SourceSafe客户端登陆问题 呵呵,几天没来,这里全变样了,不错不错,漂亮多了。另外,提点建议。 在C/C++ 程序中怎样延时? 用sql语句,怎样取最后10条记录, 然后是最后第20-10条 , 30-20条 cockhorse(木马)来拿分,另找WindowsCE.net 我刚请我1个要好的朋友(但是很穷)吃饭 如何清空数据日志呢? 没人回答,我再问一遍关于ADODataSet中参数传递的问题 如何删除一个被定义主键的表 关于ListBox,请教!(在线等待) tomcat4配置问题!请高手指教! jsp程序如保加密呀让其它人查看不到原文件,但能在服务器上运行. 请问: 有时将Dephi5最小化,作了别的windows操作后,再返回时提示………… _RecordsetPtr的问题,急……… 有关键盘和鼠标的问题 大家好!一个新手的问题。希望得到大家的帮助! 关于COM和ALT有什么好书吗? select max()的语法怎么用,怎么就是不对。 怎么才能够给在程序运行时动态生成的控件添加事件代码?请教有方法吗? WIN2000路由nat的问题 拿到scjp,工资起价要多少? 大家好!一个新手的问题。希望得到大家的帮助! 在大连找做BCB很难,也很苦恼,难道就没人用他吗??谁知道有没有大连公司用BCB。请回答 我将十分关注!!!!!!!!!! 有谁知道j2ee服务器EJB的客户端代码是怎样的? 那位高手帮我做个毕设(销售管理系统) to txinfo(冰炎两重天) To; net_lover(孟子E章),这个函数没有得到任何值,而且提示网页Javascript出错了! 我忘了开多次方 的函数了请告之! 建表问题 我用wise install 打包vb6的程序,可runtimes打不进去,请帮忙 寻李维的四套delphi电子书,在哪儿下?每套20分,第一个正确的给分 有没有人用过xa方法呀 如何在全局函数中对传入的dw参数取出其所在的window,而不另传一window参数? 请问斑竹LIULEE:为何我的Jaguar CTS 3.6.1中的菜单和对话框中的字母多是小方块 什么时候设置oracle的总内存数? Unix 字符用户界面,为什么menu库不能调用,在curses库却可以? 请问关闭显示器的语句是???? 怎样在Form2中的Command1_Click()事件CallForm1中的Command1_Click()事件? 人民日报批判“流氓兔”拉,看来“流星花园”悲剧又要上演了 偶的MM明天要去很远的地方出差了,想送点东西给她,呵呵,各位说送什么好呀? 火柴梗在燃烧时,火焰向上为什么容易熄灭? 不锈钢焊点怎么生锈了 鸡蛋在酒精中,不断加入纯水,鸡蛋会沉会浮?若在盐水中,不断加入纯水,鸡蛋会沉会浮?说酒精比纯水密度小,盐水比纯水密度大.有没有这样的规律:把密度大的液体加入密度小的液体,整个液体 火是什么?是由什么物质构成的吗,为什么谁可以熄灭火,为什么划火柴就会产生火? 凡士林进入滴定管洗不出来怎么办?可能活塞处涂多了!进入下面的小管了,洗不出来,有什么办法? 如题.凡士林纱布,酒精纱布和生理盐水纱布覆盖创面各有什么具体功效? 火柴是磷做的,火柴合上划火柴的哪部分是什么材枓做的呀? 哪里可以买到酸式滴定管的玻璃塞?酸式滴定管上的玻璃塞没了,问了一家试剂商店,说是不单卖,我就是想看看能不能配上 电镀废液能否循环使用,如浓缩或其他方式使需要的离子浓度恢复并继续用来电镀?之前搜了一下,说每一个电解槽里的电镀废液“由于考虑电能消耗以及电镀效果等因素,一般电镀液中的金属离 实验室 划火柴和熄灭火柴的方法 如果酸式滴定管的出口被涂旋塞的凡士林严重堵塞,正确的处理方法是 二氧化碳如何变成碳酸钙 《划火柴》歌词是什么意思?《划火柴》歌词是什么意思?《划火柴》歌词是什么意思? 如果酸式滴定管出现凡士林堵塞管口,应如何处理 为什么碳酸钙通入CO2会变成CaHCO3?能不能从微观粒子反应的角度给我分析一下这个反应?化学方程式我自己知道.我就问你为什么会反应?电离平衡我也学了...细致的给我讲下谢谢 气液平衡数据在哪里查 酸式滴定管涂抹了凡士林后还是会漏,用的是白凡士林,为什么呢?试过还多次都一样大概1min一滴 碳酸钙为什么加二氧化碳会变成碳酸氢钙想知道H2CO3是怎样溶解CaCO3的。 求常压101.33kpa下 乙醇-正丙醇混合液的气液平衡组成(摩尔)与温度的关系数据本人要做化工原理的课程设计 找不到这个的气液平衡组成关系 氢氧化镁,碳酸镁组成的固体怎么和稀盐酸反应?化学式是?怎么办 碳酸钙加什么变成水和二氧化碳 圆珠笔芯的塑料部分是怎么跟金属笔头连接的?有专门的机器吗?还有笔油是怎么加进笔芯里去的? 氢氧化镁和稀硫酸反应的化学式. 凡士林是什么东西, 常压条件下乙醇-水溶液的气液平衡数据哪有? 会计账簿登记规则,一般只能用蓝黑墨水的钢笔,不得用圆珠笔和铅笔.书上说错了 凡士林是什么啊胶水,护肤品里面都有吗?它到底是什么东西啊 求 丙酮-水 的气液平衡数据!我用左化工原理的课程设计的~找了好久都找不到 滴定管在检漏时不漏为什么用的时候漏 涂凡士林怎么没用 凡士林是什么物质?大船模要用什么防水?凡士林有用吗?凡士林能用在大型航模上吗? 圆珠笔分为那几个部分例如:笔芯、外壳之类的 黄金叶含量重金属超标? Vaseline是什么意思? 偏铝酸钠溶液与氯化铵反应的化学方程式 氰化物废水中重金属超标我们公司主要处理的是氰化物和重金属,整套系统相当于有两个环节,氰化物破氰处理和重金属酸化处理,两个处理好后,同时流向反应池.我就是想问,如果氰化物废水中 中国有能力制造圆珠笔笔尖吗 盐酸分别与硫化钠,硫代硫酸钠,铝酸钠,硫酸钠溶液反应的化学方程式? PET材质证明、MSDS、SGS、都有什么作用? 用圆珠笔写字时,笔尖和纸的是什么摩擦? 某化学实验小组欲探究盐酸、氢氧化钙的化学性质,取8支试管分别用A-H编号后,做如下实验.我知道答案 某化学实验小组欲探究盐酸、氢氧化钙的化学性质,取8支试管分别用A-H编号后,做如下 MSDS与SGS哪个重要我们公司要PP塑料的,现在想做一些认证,具体的有ISO14001 MSDS SGS等等,我想问一下,MSDS和SGS哪个重要?做哪个认证比较好呢? 圆珠笔笔尖是用什么材料做的?有哪几种材料? 在一个棱长为10cm的正方体木材的相对的两个面中掏出一个边长为1cm的正方形小孔,这个图形的表面积和体积是 现有(1)钠(2)碳酸钠(3)CH3CH2OH,一定条件下能与苹果酸发生反应的物质是()A.(1)B.(1)(3)C.(1)(2)(3)D.(1)(2) 污泥的重金属用什么仪器测量 用棱长为1cm的小正方体对方称边长为10cm的大正方体,共需要小正方体()个 鸡蛋在盐水中悬浮、下沉还是漂浮?烧杯中盛有食盐水,食盐水中悬浮着一个鸡蛋.现将新鲜的萝卜块放入盐水中,过些时候,鸡蛋将上浮、下沉还是悬浮? 游离态重金属用什么仪器测定呢 硫酸铜加入碳酸钠中和碳酸钠加入硫酸铜中现象一样么都是溶液 铝基板厂家哪个通过了CCC认证、CE认证、UL认证、FCC认证、SGS和ROHS环保认证呢? 检测食品中重金属用什么仪器除了原子吸收还有什么? 钾钙钠钡和溶液反应钾钙钠钡为什么不会与金属溶液反应? 碳酸钙化学式是什么啊? 同一只鸡蛋先后放入甲、乙两杯不同浓度的盐水中,鸡蛋在甲杯处于悬浮状态,如图甲; 在乙杯处于漂浮状态,同一只鸡蛋先后放入甲、乙两杯不同浓度的盐水中,鸡蛋在甲杯处于悬浮状态,如图 铵钾钠钡钙,除了这5种元素,其余元素都不溶于水,生成沉淀, 为什么不锈钢的焊点会生锈例如不锈钢防盗网 一个鸡蛋悬浮在盐水中,若往盐水中再加一些水,鸡蛋将(上浮/悬浮/下沉)?下沉搞不懂为什么 划火柴时火柴梗向上火焰会熄灭吗会熄灭吗为什么?是不是它接触不到氧气 不锈钢焊点生锈怎么办 凡士林含有激素吗?如题
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘