Csinglelock 是鸿胪寺最初用来做什么么用的?

天极传媒:天极网全国分站
您现在的位置: &&
用VC++5.0实现多线程的调度和处理
CPCW 00:00
一多任务,多进程和多线程
---- Windows95 和WindowsNT
支持多任务调度和处理,基于该功能所提供的多任务空间,程序员可以完全控制程序中每一个片段的运行,从而编写高效率的应用程序。
所谓多任务通常包括这样两大类:多进程和多线程。进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
---- 开发多线程应用程序可以利用32 位Windows 环境提供的Win32 API
接口函数,也可以利用VC++ 中提供的MFC
类库进行开发。多线程编程在这两种方式下原理是一样的,用户可以根据需要选择相应的工具。本文重点讲述用VC++5.0
类库实现多线程调度与处理的方法以及由线程多任务所引发的同步多任务特征,最后详细解释一个实现多线程的例程。
二基于MFC 的多线程编程
---- 1 MFC 对多线程的支持
类库提供了多线程编程支持,对于用户编程实现来说更加方便。非常重要的一点就是,在多窗口线程情况下,MFC
直接提供了用户接口线程的设计。
---- MFC 区分两种类型的线程:辅助线程(Worker Thread)和用户界面线程(UserInterface
Thread)。辅助线程没有消息机制,通常用来执行后台计算和维护任务。MFC
为用户界面线程提供消息机制,用来处理用户的输入,响应用户产生的事件和消息。但对于Win32
来说,这两种线程并没有区别,它只需要线程的启动地址以便启动线程执行任务。用户界面线程的一个典型应用就是类CWinApp,大家对类CwinApp
都比较熟悉,它是CWinThread
类的派生类,应用程序的主线程是由它提供,并由它负责处理用户产生的事件和消息。类CwinThread
是用户接口线程的基本类。CWinThread
的对象用以维护特定线程的局部数据。因为处理线程局部数据依赖于类CWinThread,所以所有使用MFC
的线程都必须由MFC 来创建。例如,由run-time 函数_beginthreadex
创建的线程就不能使用任何MFC API。
---- 2 辅助线程和用户界面线程的创建和终止
---- 要创建一个线程,需要调用函数AfxBeginThread。该函数通过参数重载具有两种版本,分别对应辅助线程和用户?
线程。无论是辅助线程还是用户界面线程,都需要指定额外的参数以修改优先级,堆栈大小,创建标志和安全特性等。函数AfxBeginThread
返回指向CWinThread 类对象的指针。
创建助手线程相对简单。只需要两步:实现控制函数和启动线程。它并不必须从CWinThread
派生一个类。简要说明如下:
实现控制函数。控制函数定义该线程。当进入该函数,线程启动;退出时,线程终止。该控制函数声明如下:
UINT MyControllingFunction( LPVOID pParam );
---- 该参数是一个单精度32
位值。该参数接收的值将在线程对象创建时传递给构造函数。控制函数将用某种方式解释该值。可以是数量值,或是指向包括多个参数的结构的指针,甚至可以被忽略。如果该参数是指结构,则不仅可以将数据从调用函数传给线程,也可以从线程回传给调用函数。如果使用这样的结构回传数据,当结果准备好的时候,线程要通知调用函数。当函数结束时,应返回一个UINT
类型的值值,指明结束的原因。通常,返回0
表明成功,其它值分别代表不同的错误。
---- 2. 启动线程。由函数AfxBeginThread 创建并初始化一个CWinThread
类的对象,启动并返回该线程的地址。则线程进入运行状态。
举例说明。下面用简单的代码说明怎样定义一个控制函数以及如何在程序的其它部分使用。
UINT MyThreadProc( LPVOID pParam )
CMyObject* pObject = (CMyObject*)pP
if (pObject == NULL ||
!pObject- >IsKindOf(RUNTIME_CLASS(CMyObject)))
return -1; //非法参数
……//具体实现内容
return 0; //线程成功结束
//在程序中调用线程的函数
pNewObject = new CMyO
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)
级是实时优先级,实时优先级与普通优先级的最大区别在于相同优先级进程的运行不按照时间片轮转,而是先运行的线程就先控制,如果它不主动放弃控制,同级或低优先级的线程就无法运行。
一个线程的优先级首先属于一个类,然后是其在该类中的相对位置。线程优先级的计算可以如下式表示:
---- 线程优先级= 进程类基本优先级+ 线程相对优先级
---- 进程类的基本优先级:
&&& 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
有多个)的情况;事件同步类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_。
创建类CWinThread 的派生类CExampleThread。该类将作为本程序中使用的两个线程类:CCounterThread
和CDisplayThread
的父类。这样做的目的仅是为了共享两个线程类的共用变量和函数。
---- 在CExampleThread 的头文件中填加如下变量:
&&& CMutexesDlg * m_pO//指向类CMutexesDlg指针
&&&&&&& BOOL m_bD//用以控制线程执行
&&& void SetOwner(CMutexesDlg* pOwner)
{ m_pOwner=pO };//取类CMutexesDlg的指针
&&& 然后在构造函数当中对成员变量进行初始化:
&&&&&&& m_bDone=FALSE;//初始化允许线程运行
&&&&&&& m_pOwner=NULL;//将该指针置为空
&&&&&&& m_bAutoDelete=FALSE;//要求手动删除线程对象
创建两个线程类CCounterThread 和CdisplayThread。这两个线程类是CExampleThread
的派生类。分别重载两个线程函数中的::Run()
函数,实现各线程的任务。在这两个类当中分别加入同步访问类的锁对象sLock,这里将根据同步机制的复选与否来确定是否控制对共享资源的访问。不要忘记需要加入头文件#include
"afxmt.h"。
---- 计数线程::Run() 函数的重载代码为:
int CCounterThread::Run()
&&& BOOL fSyncC//同步机制复选检测
&&& unsigned int nN//存储字符串中整数
&&& 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(":
&&& }//结束while
&&& m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
&&& return 0;
显示线程的::Run()函数重载代码为:
int CDisplayThread::Run()
&&& BOOL fSyncC
&&& CString strB
&&& 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_strN
&&& if (fSyncChecked)
&&&&&&& sLock.Unlock();
&&& //将字符串加入到列表框中
&&& m_pOwner- >AddToListBox(strBuffer);
&&& }//结束while
&&& m_pOwner- >PostMessage(WM_CLOSE, 0, 0L);
&&& return 0;
3在CMutexesDlg的头文件中加入如下成员变量:
&&&&&&& CString m_strN//线程所要操作的资源对象
&&&&&&& CMutex m_//用于同步机制的互斥量
&&&&&&& CCounterThread* m_pCounterT//指向计数线程的指针
&&&&&&& CDisplayThread* m_pDisplayT//指向显示线程的指针
&&& 首先在对话框的初始化函数中加入如下代码对对话框进行初始化:
&&& BOOL CMutexesDlg::OnInitDialog()
&&& //初始化进程优先级组合框并置缺省为NORMAL
&&& CComboBox* pB
&&& 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()
&&& //取焦点选项
CComboBox* pBox = (CComboBox*)
GetDlgItem(IDC_PRIORITYCLASS);
&&& int nCurSel = pBox- >GetCurSel();
&&& (nCurSel)
&&& case 0:
&&&&&&& dw = IDLE_PRIORITY_CLASS;
&&& case 1:
&&& default:
&&&&&&& dw = NORMAL_PRIORITY_CLASS;
&&& case 2:
&&&&&&& dw = HIGH_PRIORITY_CLASS;
&&& case 3:
&&&&&&& dw = REALTIME_PRIORITY_CLASS;
&&& SetPriorityClass(GetCurrentProcess(), dw);//调整优先级
由于调整两个线程优先级的代码基本相似,单独设置一个函数根据不同的ID
来调整线程优先级。该函数代码为:
void CMutexesDlg::OnPriorityChange(UINT nID)
&&& ASSERT(nID == IDC_CNTRTHRDPRIORITY ||
&&&&&&& nID == IDC_DSPYTHRDPRIORITY);
&&& //取对应该ID的焦点选项
&&& CComboBox* pBox = (CComboBox*) GetDlgItem(nID);
&&& int nCurSel = pBox- >GetCurSel();
&&& switch (nCurSel)
&&& case 0:
&&&&&&& dw = (DWORD)THREAD_PRIORITY_IDLE;
&&& case 1:
&&&&&&& dw = (DWORD)THREAD_PRIORITY_LOWEST;
&&& case 2:
dw = (DWORD)THREAD_PRIORITY_BELOW_NORMAL;
&&& case 3:
&&& default:
&&&&&&& dw = (DWORD)THREAD_PRIORITY_NORMAL;
&&& case 4:
dw = (DWORD)THREAD_PRIORITY_ABOVE_NORMAL;
&&& case 5:
&&&&&&& dw = (DWORD)THREAD_PRIORITY_HIGHEST;
&&& case 6:
&&&&&&& dw =
(DWORD)THREAD_PRIORITY_TIME_CRITICAL;
&&& if (nID == IDC_CNTRTHRDPRIORITY)
&&&&&&& m_pCounterThread- >SetThreadPriority(dw);
//调整计数线程优先级
&&&&&&& m_pDisplayThread- >SetThreadPriority(dw);
//调整显示线程优先级
&&& 这样线程优先级的调整只需要根据不同的ID来调用该函数:
void CMutexesDlg::OnSelchangeDspythrdpriority()
{ OnPriorityChange(IDC_DSPYTHRDPRIORITY);}
void CMutexesDlg::OnSelchangeCntrthrdpriority()
{ OnPriorityChange(IDC_CNTRTHRDPRIORITY);}
&&& 复选线程挂起的实现代码如下:
void CMutexesDlg::OnPause()
&&& //取挂起复选框状态
&&& CButton* pCheck = (CButton*)GetDlgItem(IDC_PAUSE);
&&& BOOL bPaused = ((pCheck- >GetState() & 0x003) != 0);
&&& if (bPaused)&&& {
&&&&&&& m_pCounterThread- >SuspendThread();
&&&&&&& m_pDisplayThread- >SuspendThread();
&&& }//挂起线程
&&& else&&& {
&&&&&&& m_pCounterThread- >ResumeThread();
&&&&&&& m_pDisplayThread- >ResumeThread();
&&& }//恢复线程运行
---- 程序在::OnClose()
中实现了线程的终止。在本例程当中对线程的终止稍微复杂些。需要注意的是成员变量m_bDone
的作用,在线程的运行当中循环检测该变量的状态,最终引起线程的退出。这样线程的终止是因为函数的退出而自然终止,而非采用强行终止的方法,这样有利于系统的安全。该程序中使用了PostMessage
函数,该函数发送消息后立即返回,这样可以避免阻塞。其实现的代码为:
&&& void CMutexesDlg::OnClose()
&&& int nCount = 0;
&&& DWORD dwS
&&& //取挂起复选框状态
&&& CButton* pCheck = (CButton*) GetDlgItem(IDC_PAUSE);
&&& BOOL bPaused = ((pCheck- >GetState() & 0x003) != 0);
if (bPaused == TRUE){
&&&&&&& pCheck- >SetCheck(0);//复选取消
&&&&&&& m_pCounterThread- >ResumeThread();
//恢复线程运行
&&&&&&& m_pDisplayThread- >ResumeThread();
&&& if (m_pCounterThread != NULL){
VERIFY(::GetExitCodeThread(m_pCounterThread- >
m_hThread, &dwStatus));//取计数线程结束码
&&&&&&& if (dwStatus == STILL_ACTIVE){
&&&&&&&&&&& nCount++;
&&&&&&&&&&& m_pCounterThread-
>m_bDone = TRUE;
&&&&&&& }//如果仍为运行状态,则终止
&&&&&&& else{
&&&&&&&&&&& delete
m_pCounterT
&&&&&&&&&&& m_pCounterThread =
&&&&&&& }//如果已经终止,则删除该线程对象
&&& if (m_pDisplayThread != NULL){
VERIFY(::GetExitCodeThread(m_pDisplayThread- >
m_hThread, &dwStatus));//取显示线程结束码
&&&&&&& if (dwStatus == STILL_ACTIVE){
&&&&&&&&&&& nCount++;
&&&&&&&&&&& m_pDisplayThread-
>m_bDone = TRUE;
&&&&&&& }//如果仍为运行状态,则终止
&&&&&&& else{
&&&&&&&&&&& delete
m_pDisplayT
&&&&&&&&&&& m_pDisplayThread =
&&&&&&& }//如果已经终止,则删除该线程对象
&&& if (nCount == 0)//两个线程均终止,则关闭程序
CDialog::OnClose();
&&& else //否则发送WM_CLOSE消息
PostMessage(WM_CLOSE, 0, 0);
在例程具体实现中用到了许多函数,在这里不一一赘述,关于函数的具体意义和用法,可以查阅联机帮助。
(作者:张万春责任编辑:)
欢迎在新浪微博上关注我们
笔记本手机数码家电当前位置 &
VC中利用多线程技术实现线程之间的通信_VC技术_C语言教程
本文来自频道
当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。  
  一、 实现方法
  1、理解线程
  要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
  一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
  线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
  2、线程的管理和操作
  (一)线程的启动
  创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
  对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。
  (二)线程的优先级
  以下的CwinThread类的成员函数用于线程优先级的操作:
int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);
  上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。
  (三)线程的悬挂和恢复
  CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
  (四)结束线程
  终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:
//////////////////////////////////////////////////////////// ////
//////CtestView message handlers
/////Set to True to end thread
Bool bend=FALSE;//定义的全局变量,用于控制线程的运行;
//The Thread Function;
UINT ThreadFunction(LPVOID pParam)//线程函数
 while(!bend)
  Beep(100,100);
  Sleep(1000);
 return 0;
//////////////////////////////////////////////////////////// /
CwinThread *pT
Void CtestView::OninitialUpdate()
 hWnd=GetSafeHwnd();
 pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程
 pThread-&m_bAutoDelete=FALSE;//线程为手动删除
 Cview::OnInitialUpdate();
//////////////////////////////////////////////////////////// ////
Void CtestView::OnDestroy()
 bend=TRUE;//改变变量,线程结束
 WaitForSingleObject(pThread-&m_hThread,INFINITE);//等待线程结束
 delete pT//删除线程
 Cview::OnDestroy();
  3、线程之间的通信
  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。
  (一) 利用用户定义的消息通信
  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:
UINT ThreadFunction(LPVOID pParam)
 while(!bend)
  Beep(100,100);
  Sleep(1000);
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,
LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
 AfxMessageBox(&Thread ended.&);
 Retrun 0;
  上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
  (二)用事件对象实现通信
  在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:
//////////////////////////////////////////////////////////// ////////
Cevent threadStart ,threadE
UINT ThreadFunction(LPVOID pParam)
 ::WaitForSingleObject(threadStart.m_hObject,INFINITE);
 AfxMessageBox(&Thread start.&);
 while(!bend)
  Beep(100,100);
  Sleep(1000);
  Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
  //等待threadEnd事件有信号,无信号时线程在这里悬停
  If(result==Wait_OBJECT_0)
   Bend=TRUE;
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
//////////////////////////////////////////////////////////// /
Void CtestView::OninitialUpdate()
 hWnd=GetSafeHwnd();
 threadStart.SetEvent();//threadStart事件有信号
 pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
 pThread-&m_bAutoDelete=FALSE;
 Cview::OnInitialUpdate();
//////////////////////////////////////////////////////////// ////
Void CtestView::OnDestroy()
 threadEnd.SetEvent();
 WaitForSingleObject(pThread-&m_hThread,INFINITE);
 delete pT
 Cview::OnDestroy();
  运行这个程序,当关闭程序时,才显示提示框,显示&Thread ended&。
  4、线程之间的同步
  前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
  (一) 临界区
  临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:
#include &afxmt.h&
int array[10],destarray[10];
CCriticalSection S
UINT WriteThread(LPVOID param)
 Section.Lock();
 for(int x=0;x&10;x++)
  array[x]=x;
 Section.Unlock();
UINT ReadThread(LPVOID param)
 Section.Lock();
 For(int x=0;x&10;x++)
  Destarray[x]=array[x];
  Section.Unlock();
  上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
  (二)互斥
  互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:
#include &afxmt.h&
int array[10],destarray[10];
UINT WriteThread(LPVOID param)
 CsingleL
 singlelock (&Section);
 singlelock.Lock();
 for(int x=0;x&10;x++)
  array[x]=x;
 singlelock.Unlock();
UINT ReadThread(LPVOID param)
 CsingleL
 singlelock (&Section);
 singlelock.Lock();
 For(int x=0;x&10;x++)
  Destarray[x]=array[x];
  singlelock.Unlock();
  (三)信号量
  信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。
//////////////////////////////////////////////////////////// /////////////
Csemaphore *
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
UINT ThreadProc1(LPVOID param)
 CsingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,&Thread1 had access&,&Thread1&,MB_OK);
 return 0;
UINT ThreadProc2(LPVOID param)
 CSingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,&Thread2 had access&,&Thread2&,MB_OK);
 return 0;
UINT ThreadProc3(LPVOID param)
 CsingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,&Thread3 had access&,&Thread3&,MB_OK);
 return 0;
  二、 编程步骤
  1、 启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为&sequence&
  2、 输入要排续的数字,声明四个子线程;
  3、 输入代码,编译运行程序。
三、 程序代码
//////////////////////////////////////////////////////////// //////////////////////////
// sequence.cpp : Defines the entry point for the console application.
主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;
线程建立函数:
HANDLE CreateThread(
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;
 DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;
 LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针;
 LPVOID lpParameter, // 传递给线程函数的参数,可以保存一个指针值;
 DWORD dwCreationFlags, // 线程建立是的初始标记,运行或挂起;
 LPDWORD lpThreadId // 指向接收线程号的DWORD变量;
对临界资源控制的多线程控制的信号函数:
HANDLE CreateEvent(
 LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;
 BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。若为 FALSE则在WaitForSingleObject
 //后,系统自动清除事件信号;
 BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号;
 LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH;
 //如果遇到同名的其他信号量函数就会失败,如果遇
 //到同类信号同名也要注意变化;
HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性结构指针,可为NULL
 BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,
 //这样其他线程就不能获得此互斥量也就无法进入由
 //该互斥量控制的临界区。FALSE表示不占有该互斥量
 LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH如果
 //遇到同名的其他信号量函数就会失败,
 //如果遇到同类信号同名也要注意变化;
//初始化临界区信号,使用前必须先初始化
VOID InitializeCriticalSection(
 LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针
//阻塞函数
//如果等待的信号量不可用,那么线程就会挂起,直到信号可用
//线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后
//Event信号会变得无信号,Mutex、Semaphore等也会变。
DWORD WaitForSingleObject(
 HANDLE hHandle, // 等待对象的句柄
 DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待
//如果要等待多个信号可以使用WaitForMutipleObject函数
#include &stdafx.h&
#include &stdlib.h&
#include &memory.h&
HANDLE evtT //事件信号,标记是否所有子线程都执行完
下面使用了三种控制方法,你可以注释其中两种,使用其中一种。
注意修改时要连带修改临界区PrintResult里的相应控制语句
HANDLE evtP //事件信号,标记事件是否已发生
//CRITICAL_SECTION csP //临界区
//HANDLE mtxP //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号
static long ThreadCompleted = 0;
/*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)进行加减操作*/
//下面的结构是用于传送排序的数据给各个排序子线程
struct MySafeArray
//打印每一个线程的排序结果
void PrintResult(long* Array, int iLength, const char* HeadStr = &sort&);
//排序函数
unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序
unsigned long __stdcall SelectSort(void* theArray); //选择排序
unsigned long __stdcall HeapSort(void* theArray); //堆排序
unsigned long __stdcall InsertSort(void* theArray); //插入排序
/*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread
建立一个线程。
(1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责
栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall
(2)返回值必须是unsigned long
(3)参数必须是一个32位值,如一个指针值或long类型
(4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中):
static unsigned long _stdcall MyThreadFun(void* pParam);
handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);
之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/
int QuickSort(long* Array, int iLow, int iHigh); //快速排序
int main(int argc, char* argv[])
 long data[] = {123,34,546,754,34,74,3,56};
 int iDataLen = 8;
 //为了对各个子线程分别对原始数据进行排序和保存排序结果
 //分别分配内存对data数组的数据进行复制
 long *data1, *data2, *data3, *data4, *data5;
 MySafeArray StructData1, StructData2, StructData3, StructData4;
 data1 = new long[iDataLen];
 memcpy(data1, data, iDataLen && 2); //把data中的数据复制到data1中
 //内存复制 memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度
 //为4字节,所以复制的字节数为iDataLen && 2, 即等于iDataLen*4
 StructData1.data = data1;
 StructData1.iLength = iDataL
 data2 = new long[iDataLen];
 memcpy(data2, data, iDataLen && 2);
 StructData2.data = data2;
 StructData2.iLength = iDataL
 data3 = new long[iDataLen];
 memcpy(data3, data, iDataLen && 2);
 StructData3.data = data3;
 StructData3.iLength = iDataL
 data4 = new long[iDataLen];
 memcpy(data4, data, iDataLen && 2);
 StructData4.data = data4;
 StructData4.iLength = iDataL
 data5 = new long[iDataLen];
 memcpy(data5, data, iDataLen && 2);
 unsigned long TID1, TID2, TID3, TID4;
 //对信号量进行初始化
 evtTerminate = CreateEvent(NULL, FALSE, FALSE, &Terminate&);
 evtPrint = CreateEvent(NULL, FALSE, TRUE, &PrintResult&);
 //分别建立各个子线程
 CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);
 CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);
 CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);
 CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);
 //在主线程中执行行快速排序,其他排序在子线程中执行
 QuickSort(data5, 0, iDataLen - 1);
 PrintResult(data5, iDataLen, &Quick Sort&);
 WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子线程结束
 //所有的子线程结束后,主线程才可以结束
 delete[] data1;
 delete[] data2;
 delete[] data3;
 delete[] data4;
 CloseHandle(evtPrint);
 return 0;
冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次
循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/
unsigned long __stdcall BubbleSort(void* theArray)
 long* Array = ((MySafeArray*)theArray)-&
 int iLength = ((MySafeArray*)theArray)-&iL
 int i, j=0;
 for (i = iLength-1; i &0; i--)
  for(j = 0; j & j++)
   if(Array[j] &Array[j+1]) //前比后大,交换
    swap = Array[j];
    Array[j] = Array[j+1];
    Array[j+1] =
 PrintResult(Array, iLength, &Bubble Sort&); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
/*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。*/
unsigned long __stdcall SelectSort(void* theArray)
 long* Array = ((MySafeArray*)theArray)-&
 int iLength = ((MySafeArray*)theArray)-&iL
 long lMin, lS
 int i, j, iMinP
 for(i=0; i & iLength-1; i++)
  lMin = A
  iMinPos =
  for(j=i + 1; j &= iLength-1; j++) //从无序的元素中找出最小的元素
   if(Array[j] & lMin)
    iMinPos =
    lMin = Array[j];
  //把选出的元素交换拼接到有序序列的最后
  lSwap = A
  Array = Array[iMinPos];
  Array[iMinPos] = lS
 PrintResult(Array, iLength, &Select Sort&); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
/*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/
unsigned long __stdcall HeapSort(void* theArray)
 long* Array = ((MySafeArray*)theArray)-&
 int iLength = ((MySafeArray*)theArray)-&iL
 int i, j,
 for(i=0; i {
  for(j = iLength - 1; j&i; j--) //从最后倒数上去比较字节点和父节点
   p = (j - i - 1)/2 + //计算父节点数组下标
   //注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减
   if(Array[j] & Array
) //如果父节点数值大则交换父节点和字节点
    swap = Array[j];
    Array[j] = Array
    Array
 PrintResult(Array, iLength, &Heap Sort&); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
/*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/
unsigned long __stdcall InsertSort(void* theArray)
 long* Array = ((MySafeArray*)theArray)-&
 int iLength = ((MySafeArray*)theArray)-&iL
 int i=1, j=0;
 for(i=1; i {
  temp = A //取出序列后面无序数据的第一个元素值
  for(j=i; j&0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置
   if(Array[j - 1] &temp) //如果该元素比插入值大则后移
    Array[j] = Array[j - 1];
   else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置
  Array[j] =
 PrintResult(Array, iLength, &Insert Sort&); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
  //若都执行完则设置程序结束信号量
 return 0;
/*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右
边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归
建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。 这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/
int QuickSort(long* Array, int iLow, int iHigh)
 if(iLow &= iHigh) return 1; //递归结束条件
 long pivot = Array[iLow];
 int iLowSaved = iLow, iHighSaved = iH //保未改变的iLow,iHigh值保存起来
 while (iLow & iHigh)
  while (Array[iHigh] &= pivot && iHigh &iLow) //寻找比支点大的元素
   iHigh -- ;
  Array[iLow] = Array[iHigh]; //把找到的元素放置到空置的位置
  while (Array[iLow] & pivot && iLow & iHigh) //寻找比支点小的元素
   iLow ++ ;
  Array[iHigh] = Array[iLow]; //把找到的元素放置到空置的位置
 Array[iLow] = //把支点值放置到支点位置,这时支点位置是空置的
 //对左右部分分别进行递归处理
 QuickSort(Array, iLowSaved, iHigh-1);
 QuickSort(Array, iLow+1, iHighSaved);
 return 0;
//每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程
//竞争对控制台的使用权。
void PrintResult(long* Array, int iLength, const char* HeadStr)
 WaitForSingleObject(evtPrint, INFINITE); //等待事件有信号
 //EnterCriticalSection(&csPrint); //标记有线程进入临界区
 //WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(没有线程拥有它)
 printf(&%s: &, HeadStr);
 for (i=0; i {
  printf(&%d,&, Array);
  Sleep(100); //延时(可以去掉)
/*只是使得多线程对临界区访问的问题比较容易看得到
如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会
分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果
 printf(&%d\n&, Array);
 SetEvent(evtPrint); //把事件信号量恢复,变为有信号
  四、 小结
  对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。
视频教程列表
文章教程搜索
输入您的搜索字词
提交搜索表单
C语言程序设计推荐教程
tel:<font color="#FF30058

我要回帖

更多关于 csinglelock使用方法 的文章

 

随机推荐