!pObject- >IsKindOf(RUNTIME_CLASS(CMyObject)))
return -1; //非法参数
……//具体实现内容
return 0; //线程成功结束
}
//在程序中调用线程的函数
……
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);
……
创建用户界面线程有两种方法。
---- 第一种方法,首先从CWinTread 类派生一个类(注意必须要用宏DECLARE_DYNCREATE 和IMPLEMENT_DYNCREATE 对该类进行声明和实现);然后调用函数AfxBeginThread 创建CWinThread 派生类的对象进行初始化启动线程运行。除了调用函数AfxBeginThread 之外, 也可以采用第二种方法,即先通过构造函数创建类CWinThread 的一个对象,然后由程序员调用函数::CreateThread 来启动线程。通常类CWinThread 的对象在该线程的生存期结束时将自动终止,如果程序员希望自己来控制,则需要将m_bAutoDelete 设为FALSE。这样在线程终止之后类CWinThread 对象仍然存在,只是在这种情况下需要手动删除CWinThread 对象。
---- 通常线程函数结束之后,线程将自行终止。类CwinThread 将为我们完成结束线程的工作。如果在线程的执行过程中程序员希望强行终止线程的话,则需要在线程内部调用AfxEndThread(nExitCode)。其参数为线程结束码。这样将终止线程的运行,并释放线程所占用的资源。如果从另一个线程来终止该线程,则必须在两个线程之间设置通信方法。如果从线程外部来终止线程的话,还可以使用Win32 函数(CWinThread 类不提供该成员函数):BOOL TerminateThread(HANDLE hThread,DWORD dwExitcode)。但在实际程序设计中对该函数的使用一定要谨慎,因为一旦该命令发出,将立即终止该线程,并不释放线程所占用的资源,这样可能会引起系统不稳定。
---- 如果所终止的线程是进程内的最后一个线程,则在该线程终止之后进程也相应终止。
---- 3 进程和线程的优先级问题
---- 在Windows95 和WindowsNT 操作系统当中,任务是有优先级的,共有32 级,从0 到31,系统按照不同的优先级调度线程的运行。
---- 1) 0-15 级是普通优先级,线程的优先级可以动态变化。高优先级线程优先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线程按照时间片轮流运行。2) 16-30 级是实时优先级,实时优先级与普通优先级的最大区别在于相同优先级进程的运行不按照时间片轮转,而是先运行的线程就先控制CPU,如果它不主动放弃控制,同级或低优先级的线程就无法运行。
---- 一个线程的优先级首先属于一个类,然后是其在该类中的相对位置。线程优先级的计算可以如下式表示:
---- 线程优先级= 进程类基本优先级+ 线程相对优先级
---- 进程类的基本优先级:
IDLE_PROCESS_CLASS
NORMAL_PROCESS_CLASS
HIGH_PROCESS_CLASS
REAL_TIME_PROCESS_CLASS
线程的相对优先级:
THREAD_PRIORITY_IDLE
(最低优先级,仅在系统空闲时执行)
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_NORMAL (缺省)
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_CRITICAL
(非常高的优先级)
---- 4 线程同步问题
---- 编写多线程应用程序的最重要的问题就是线程之间的资源同步访问。因为多个线程在共享资源时如果发生访问冲突通常会产生不正确的结果。例如,一个线程正在更新一个结构的内容的同时另一个线程正试图读取同一个结构。结果,我们将无法得知所读取的数据是什么状态:旧数据,新数据,还是二者的混合?
---- MFC 提供了一组同步和同步访问类来解决这个问题,包括:
---- 同步对象:CSyncObject, CSemaphore, CMutex, CcriticalSection 和CEvent ;同步访问对象:CMultiLock 和CSingleLock 。
---- 同步类用于当访问资源时保证资源的整体性。其中CsyncObject 是其它四个同步类的基类,不直接使用。信号同步类CSemaphore 通常用于当一个应用程序中同时有多个线程访问一个资源(例如,应用程序允许对同一个Document 有多个View)的情况;事件同步类CEvent 通常用于在应用程序访问资源之前应用程序必须等待(比如,在数据写进一个文件之前数据必须从通信端口得到)的情况;而对于互斥同步类CMutex 和临界区同步类CcriticalSection 都是用于保证一个资源一次只能有一个线程访问,二者的不同之处在于前者允许有多个应用程序使用该资源(例如,该资源在一个DLL 当中)而后者则不允许对同一个资源的访问超出进程的范畴,而且使用临界区的方式效率比较高。
---- 同步访问类用于获得对这些控制资源的访问。CMultiLock 和CSingleLock 的区别仅在于是需要控制访问多个还是单个资源对象。
---- 5 同步类的使用方法
---- 解决同步问题的一个简单的方法就是将同步类融入共享类当中,通常我们把这样的共享类称为线程安全类。下面举例来说明这些同步类的使用方法。比如,一个用以维护一个帐户的连接列表的应用程序。该应用程序允许3 个帐户在不同的窗口中检测,但一次只能更新一个帐户。当一个帐户更新之后,需要将更新的数据通过网络传给一个数据文档。
---- 该例中将使用3 种同步类。由于允许一次检测3 个帐户,使用CSemaphore 来限制对3 个视窗对象的访问。当更新一个帐目时,应用程序使用CCriticalSection 来保证一次只有一个帐目更新。在更新成功之后,发CEvent 信号,该信号释放一个等待接收信号事件的线程。该线程将新数据传给数据文档。
---- 要设计一个线程安全类,首先根据具体情况在类中加入同步类做为数据成员。在例子当中,可以将一个CSemaphore 类的数据成员加入视窗类中,一个CCriticalSection 类数据成员加入连接列表类,而一个CEvent 数据成员加入数据存储类中。
---- 然后,在使用共享资源的函数当中,将同步类与同步访问类的一个锁对象联系起来。即,在访问控制资源的成员函数中应该创建一个CSingleLock 或CMultiLock 的对象并调用该对象的Lock 函数。当访问结束之后,调用UnLock 函数,释放资源。
---- 用这种方式来设计线程安全类比较容易。在保证线程安全的同时,省去了维护同步代码的麻烦,这也正是OOP 的思想。但是使用线程安全类方法编程比不考虑线程安全要复杂,尤其体现在程序调试过程中。而且线程安全编程还会损失一部分效率,比如在单CPU 计算机中多个线程之间的切换会占用一部分资源。
三编程实例
---- 下面以VC++5.0 中一个简单的基于对话框的MFC 例程来说明实现多线程任务调度与处理的方法,下面加以详细解释。
---- 在该例程当中定义两个用户界面线程,一个显示线程(CDisplayThread) 和一个计数线程(CCounterThread)。这两个线程同时操作一个字符串变量m_strNumber,其中显示线程将该字符串在一个列表框中显示,而计数线程则将该字符串中的整数加1。在例程中,可以分别调整进程、计数线程和显示线程的优先级。例程中的同步机制使用CMutex 和CSingleLock 来保证两个线程不能同时访问该字符串。同步机制执行与否将明显影响程序的执行结果。在该例程中允许将将把两个线程暂时挂起,以查看运行结果。例程中还允许查看计数线程的运行。该例程中所处理的问题也是多线程编程中非常具有典型意义的问题。
---- 在该程序执行时主要有三个用于调整优先级的组合框,三个分别用于选择同步机制、显示计数线程运行和挂起线程的复选框以及一个用于显示运行结果的列表框。
---- 在本程序中使用了两个线程类CCounterThread 和CDisplayThread,这两个线程类共同操作定义在CMutexesDlg 中的字符串对象m_strNumber。本程序对同步类CMutex 的使用方法就是按照本文所讲述的融入的方法来实现的。同步访问类CSingleLock 的锁对象则在各线程的具体实现中定义。
---- 下面介绍该例程的具体实现:
利用AppWizard 生成一个名为Mutexes 基于对话框的应用程序框架。
利用对话框编辑器在对话框中填加以下内容:三个组合框,三个复选框和一个列表框。三个组合框分别允许改变进程优先级和两个线程优先级,其ID 分别设置为:IDC_PRIORITYCLASS、IDC_DSPYTHRDPRIORITY 和IDC_CNTRTHRDPRIORITY。三个复选框分别对应着同步机制选项、显示计数线程执行选项和暂停选项,其ID 分别设置为IDC_SYNCHRONIZE、IDC_SHOWCNTRTHRD 和IDC_PAUSE。列表框用于显示线程显示程序中两个线程的共同操作对象m_strNumber,其ID 设置为IDC_DATABOX。
创建类CWinThread 的派生类CExampleThread。该类将作为本程序中使用的两个线程类:CCounterThread 和CDisplayThread 的父类。这样做的目的仅是为了共享两个线程类的共用变量和函数。
---- 在CExampleThread 的头文件中填加如下变量:
CMutexesDlg * m_pOwner;//指向类CMutexesDlg指针
BOOL m_bDone;//用以控制线程执行
及函数:
void SetOwner(CMutexesDlg* pOwner)
{ m_pOwner=pOwner; };//取类CMutexesDlg的指针
然后在构造函数当中对成员变量进行初始化:
m_bDone=FALSE;//初始化允许线程运行
m_pOwner=NULL;//将该指针置为空
m_bAutoDelete=FALSE;//要求手动删除线程对象
创建两个线程类CCounterThread 和CdisplayThread。这两个线程类是CExampleThread 的派生类。分别重载两个线程函数中的::Run() 函数,实现各线程的任务。在这两个类当中分别加入同步访问类的锁对象sLock,这里将根据同步机制的复选与否来确定是否控制对共享资源的访问。不要忘记需要加入头文件#include "afxmt.h"。
---- 计数线程::Run() 函数的重载代码为:
int CCounterThread::Run()
{
BOOL fSyncChecked;//同步机制复选检测
unsigned int nNumber;//存储字符串中整数
if (m_pOwner == NULL)
return -1;
//将同步对象同锁对象联系起来
CSingleLock sLock(&(m_pOwner- >m_mutex));
while (!m_bDone)//控制线程运行,为终止线程服务
{
//取同步机制复选状态
fSyncChecked = m_pOwner- >
IsDlgButtonChecked(IDC_SYNCHRONIZE);
//确定是否使用同步机制
if (fSyncChecked)
sLock.Lock();
//读取整数
_stscanf((LPCTSTR) m_pOwner- >m_strNumber,
_T("%d"), &nNumber);
nNumber++;//加1
m_pOwner- >m_strNumber.Empty();//字符串置空
while (nNumber != 0) //更新字符串
{
m_pOwner- >m_strNumber +=
(TCHAR) ('0'+nNumber%10);
nNumber /= 10;
}
//调整字符串顺序
m_pOwner- >m_strNumber.MakeReverse();
//如果复选同步机制,释放资源
if (fSyncChecked)
sLock.Unlock();
//确定复选显示计数线程
if (m_pOwner- >IsDlgButtonChecked(IDC_SHOWCNTRTHRD))
m_pOwner- >AddToListBox(_T("Counter: Add 1"));
}//结束while
m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
return 0;
}
显示线程的::Run()函数重载代码为:
int CDisplayThread::Run()
{
BOOL fSyncChecked;
CString strBuffer;
ASSERT(m_pOwner != NULL);
if (m_pOwner == NULL)
return -1;
CSingleLock sLock(&(m_pOwner- >m_mutex));
while (!m_bDone)
{
fSyncChecked = m_pOwner- >
IsDlgButtonChecked(IDC_SYNCHRONIZE);
if (fSyncChecked)
sLock.Lock();
//构建要显示的字符串
strBuffer = _T("Display: ");
strBuffer += m_pOwner- >m_strNumber;
if (fSyncChecked)
sLock.Unlock();
//将字符串加入到列表框中
m_pOwner- >AddToListBox(strBuffer);
}//结束while
m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
return 0;
}
3在CMutexesDlg的头文件中加入如下成员变量:
CString m_strNumber;//线程所要操作的资源对象
CMutex m_mutex;//用于同步机制的互斥量
CCounterThread* m_pCounterThread;//指向计数线程的指针
CDisplayThread* m_pDisplayThread;//指向显示线程的指针
首先在对话框的初始化函数中加入如下代码对对话框进行初始化:
BOOL CMutexesDlg::OnInitDialog()
{
……
//初始化进程优先级组合框并置缺省为NORMAL
CComboBox* pBox;
pBox = (CComboBox*) GetDlgItem(IDC_PRIORITYCLASS);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("High"));
pBox- >AddString(_T("Realtime"));
pBox- >SetCurSel(1);
}
//初始化显示线程优先级组合框并置缺省为NORMAL
pBox = (CComboBox*) GetDlgItem(IDC_DSPYTHRDPRIORITY);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Lowest"));
pBox- >AddString(_T("Below normal"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("Above normal"));
pBox- >AddString(_T("Highest"));
pBox- >AddString(_T("Timecritical"));
pBox- >SetCurSel(3);
}
//初始化计数线程优先级组合框并置缺省为NORMAL
pBox = (CComboBox*) GetDlgItem(IDC_CNTRTHRDPRIORITY);
ASSERT(pBox != NULL);
if (pBox != NULL){
pBox- >AddString(_T("Idle"));
pBox- >AddString(_T("Lowest"));
pBox- >AddString(_T("Below normal"));
pBox- >AddString(_T("Normal"));
pBox- >AddString(_T("Above normal"));
pBox- >AddString(_T("Highest"));
pBox- >AddString(_T("Timecritical"));
pBox- >SetCurSel(3);
}
//初始化线程挂起复选框为挂起状态
CButton* pCheck = (CButton*) GetDlgItem(IDC_PAUSE);
pCheck- >SetCheck(1);
//初始化线程
m_pDisplayThread = (CDisplayThread*)
AfxBeginThread(RUNTIME_CLASS(CDisplayThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
m_pDisplayThread- >SetOwner(this);
m_pCounterThread = (CCounterThread*)
AfxBeginThread(RUNTIME_CLASS(CCounterThread),
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
m_pCounterThread- >SetOwner(this);
……
}
然后填加成员函数:
void AddToListBox(LPCTSTR szBuffer);//用于填加列表框显示
该函数的实现代码为:
void CMutexesDlg::AddToListBox(LPCTSTR szBuffer)
{
CListBox* pBox = (CListBox*) GetDlgItem(IDC_DATABOX);
ASSERT(pBox != NULL);
if (pBox != NULL){
int x = pBox- >AddString(szBuffer);
pBox- >SetCurSel(x);
if (pBox- >GetCount() > 100)
pBox- >DeleteString(0);
}
}
---- 然后利用ClassWizard 填加用于调整进程优先级、两个线程优先级以及用于复选线程挂起的函数。
---- 调整进程优先级的代码为:
void CMutexesDlg::OnSelchangePriorityclass()
{
DWORD dw;
//取焦点选项
CComboBox* pBox = (CComboBox*)
GetDlgItem(IDC_PRIORITYCLASS);
int nCurSel = pBox- >GetCurSel();
switch (nCurSel)
{
case 0:
dw = IDLE_PRIORITY_CLASS;break;
case 1:
default:
dw = NORMAL_PRIORITY_CLASS;break;
case 2:
dw = HIGH_PRIORITY_CLASS;break;
case 3:
dw = REALTIME_PRIORITY_CLASS;break;
}
SetPriorityClass(GetCurrentProcess(), dw);//调整优先级
}
---- 由于调整两个线程优先级的代码基本相似,单独设置一个函数根据不同的ID 来调整线程优先级。该函数代码为:
void CMutexesDlg::OnPriorityChange(UINT nID)
{
ASSERT(nID == IDC_CNTRTHRDPRIORITY (北联网教程,专业提供视频软件下载)
……