1、基础知识:
1)进程 (process):是一个具有一定功能的程序在一个数据集合的一次动态执行过程。进程由正文段,用户数据段以及系统数据段共同组成一个执行环境,与处理器、存储器和外设等资源的分配和回收相对应,进程是计算机系统资源的使用主体,也是操作系统分配资源的基本单位。
2)线程:在多个进程并发执行时,进程切换的开销比较大,影响了进程间通信的效率。因此提出了更小的能独立运行的基本单位——线程。线程时进程的一个实体,是cpu调度和分配的基本单位,除了一些在运行中必不可少的资源,线程不拥有系统资源,但是线程可以和同属于一个进程的其他线程共享进程的全部资源。
3)传统的操作系统中,进程是分配资源、独立调度和分配的基本单位,引入线程后,线程当作调度和分配的基本单位,进程仍然是拥有资源的基本单位。
4)关系:为了让进程完成一些工作,进程必须至少占有一个线程,所以线程是描述进程内的执行,正是线程负责执行包含在进程的地址空间的代码。
5)互锁函数: 运行在用户模式,它能保证当一个线程访问一个变量时,其他线程无法访问此变量,以确保变量值的唯一性。这种访问方式叫做原子访问。如InterlockedIncrement(LPLONG)等等。
6)事件对象:事件对象运行在内核模式,利用等待函数来等待所需的事件、信号,在等待的过程中,线程处于睡眠态,当接收到信号后,内核恢复线程的运行。等待函数如:WaitForSingleObject、WaitForMultipleObjects等四个。和事件有关的函数:CreateEvent、SetEvent 、PulseEvent 、ResetEvent 、OpenEvent等。
其中,CreateEvent创建一个事件对象,参数1必须为NULL,参数2指定是否手工重新设置事件对象的状态,如果为FALSE,则当等待函数接到信号并返回后此事件对象被自动置为无效,这时等待此事件对象的其他线程就不会被唤醒;如果为TRUE,则不会被置为无效,其他等待此事件对象的的线程也将被唤醒。
ResetEvent函数可以手工将事件对象置为无效。
SetEvent函数将事件对象置为有效。
OpenEvent打开已经创建的事件对象,一般用于不同进程内的线程同步。
2、线程的编程技术
1)编写线程函数,其必须有如小原型:
DWORD WINAPI MyThread(LPVOID lpvThreadParm);
注意只能有一个参数,这个函数不能由用户调用,由操作系统调用一个内部函数如StartOfThread。
2)创建线程:一个线程的主线程由操作系统自动生成,由主线程创建其他线程用CreateThread函数。
3)终止线程:某线程调用了ExitThread函数,终止自己;调用TerminateThread函数可以终止指定的线程。
4)其他函数:设定优先级SetThreadPriority、挂起线程SuspendThread、恢复线程ResumeThread。
Windows CE多线程编程包括线程的启动、线程的运行状态控制、线程同步及数据通信和线程的正常/非正常退出。本项目的软件及架构在多线程设计上,要求通过多线程实现异步的数据采集及绘制,以提高系统运行效率。
1. 线程的启动
Win32API提供支持多线程的启动,调用API函数CreateThread()分配资源启动线程,并返回线程句柄(Handle),以控制线程状态。客观上,这种方法在Win32平台上是通用的。然而,通用不一定最好。函数原型:<winbase.h>
HANDLE
WINAPI
CreateThread (
LPSECURITY_ATTRIBUTES lpsa,
//
security属性,在WinCE下须为NULL
DWORD cbStack,
//
堆栈大小,除非定义宏,否则被忽略
LPTHREAD_START_ROUTINE lpStartAddr,
//
起始地址,即C的函数指针
LPVOID lpvThreadParam,
//
自定义的传入线程参数
DWORD fdwCreate,
//
标识线程是否立即运行,默认是
LPDWORD lpIDThread
//
新线程ID
);
线程自身应用return结束,也可使用ExitThread()结束;主线程可以使用WaitForSingleObject(hThreadHandle, dwMilliseconds)等待线程结束,然后使用GetExitCode()获得返回码,最后使用CloseHandle()释放核心对象
Window C Runtime Library提供了封装CreateThread的函数_beginthreadex(),使得CRT对线程可以进行登记。函数原型一般在<process.h>中,但Windows SDK4.2没有对此函数的定义。
MFC是在eVC下提供的类库,基本上以MFC程序框架下封装了大多API系统调用,优点不仅仅是使用方便,更重要的是对系统的运行稳定性、正确性都提供的更高的保障。通过查阅得知,通过MFC提供的封装API函数AfxCreateThread()创建新线程,返回MFC的CWinThread类对象实例,这样通过对象实例可以更方便安全的进行线程的行为控制。函数原型为:<afxwin.h>
CWinThread
*
AFXAPI AfxBeginThread(
//
创建工作者线程重载方法
AFX_THREADPROC pfnThreadProc,
//
线程起始函数
LPVOID pParam,
//
自定义参数
int
nPriority
=
THREAD_PRIORITY_NORMAL,
//
优先级
UINT nStackSize
=
0
,
//
栈大小
DWORD dwCreateFlags
=
0
,
//
标识线程是否立即运行,默认是
LPSECURITY_ATTRIBUTES lpSecurityAttrs
=
NULL
//
安全属性
);

CWinThread
*
AFXAPI AfxBeginThread( // 创建UI线程
CRuntimeClass
*
pThreadClass,
//
指定线程类,为CWinThread子类
int
nPriority
=
THREAD_PRIORITY_NORMAL,
UINT nStackSize
=
0
,
DWORD dwCreateFlags
=
0
,
LPSECURITY_ATTRIBUTES lpSecurityAttrs
=
NULL
);
线程初始化时可以传递参数,在Window CE下线程函数标准声明为UINT FunctionName(PVOID pArg),在CreateThread时将pArg对应实参指定。但传参注意MFC不是线程安全类的指针。本系统传递CDialog指针,因此通过类型转换,即在CDialog子类方法中调用AfxBeginThread(SubThread,(LPVOID)this,0,0,0,0)。
2. 线程的状态
线程的状态,一般有就绪态、运行台、阻塞态、终止态等。为了实现各线程的功能同步,有必要合理的是各个线程进行状态的切换。在MFC的线程框架下,通过::AfxCreateThread()启动线程、自身CWinTherad::Suspend()阻塞线程、其他线程调用CWinThread::Resume()重新就绪线程、线程return正常退出。考虑到本嵌入式系统对稳定性、效率的高要求,去掉不安全的调用,如CWinThread::Terminate()外部调用终止线程等方法。
对于线程的结束,MFC建议线程自身通过return正常结束,而TerminateThread()则简单的收回线程控制块资源,极可能造成线程内部资源不能释放,是系统资源泄漏、变得不稳定。
3. 线程的同步
WindowsCE提供若干线程通信机制,本系统选用Event(事件)进行线程间的同步。通过CreateEvent()申请Event资源,返回HANFLE实例,以进行行为控制。在申请时即可指定生成时Event状态以及复位机制。本系统中信号生成时为复位状态,有且仅有手动调用ResetEvent()才能复位——即手动方式,则在生成时参数为:CreateEvent(NULL,TRUE,FALSE,NULL)。
对线程控制通过判断各Event资源信号的置位/复位实现。一个特点就是Event资源的静态优先级:多Event的判断通过WaitForMultipleObjects(3,signals,FALSE,-1)实现,对于Event数组signals装入3个Event并按优先级排列HANDLE signals[3] = { hCloseEvent, hPauseSampleEvent, hBeginSampleEvent},如果三个Event同时置位,则首先响应hCloseEvent,实现了线程关闭的可靠性。
线程在某种条件下要进行状态切换,或者是自身行为或者是外界行为。首先线程外部创建线程,并指定线程初始状态。线程内部由于某种条件不满足,需要等待其他线程而挂起时自身调用CWindThread::Suspend()。然后需通过线程外界调用CWinThread::Resume(),使被唤醒线程进入就绪态,准备从CWinThread::Suspend()处继续执行。API的Sleep()可以是当前线程阻塞一定时间,在某些环境下(如与驱动交互时有意延时,以便驱动有时间操作硬件)可以用到。
4. 线程的优先级
本系统为及时响应驱动的数据传递信号,将与之相关的线程SubThread提高优先级,通过CWindThread:: SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL)实现。但Windows CE严格按优先级进行调度,因此SubThread一般在WaitForMultipleObjects()阻塞状态,才能使其他线程得以运行。
5. 主要操作的代码示例为:
创建工作者线程并设置线程优先级:
CWinThread
*
pSubQuerySampleThread
=
AfxBeginThread(SubThread,(LPVOID)
this
,
0
,
0
,
0
,
0
);
pSubThread
->
SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);
创建Event资源:
HANDLE hCloseEvent
=
CreateEvent(NULL,TRUE,FALSE,NULL);
if
(INVALID_HANDLE_VALUE
==
hCloseEvent) {... ...}
Event的信号控制:
SetEvent(hCloseEvent);
… …
ResetEvent(hCloseEvent);
线程的Event判断及线程状态转换:
HANDLE signals[
3
]
=
{ hCloseEvent, hPauseEvent, hBeginEvent};
DWORD dwEvent
=
WaitForMultipleObjects(
3
,signals,FALSE,
-
1
);
if
(WAIT_OBJECT_0
==
dwEvent) {
return
;
}
if
(WAIT_OBJECT_0
+
1
==
dwEvent) {
SuspendThread(pSubThread
->
m_hThread);
}
pSubThread
->
ResumeThread();
6. 使用MFC进行线程同步(更新于2008-04)
原工程进行改版,软件在原有基础上更合理进行封装,并使用了MFC包装的Event和WaitForMultipleObject,即CEvent和CMultiLock。
创建CEvent,构造函数:需包含<afxmt.h>
CEvent::CEvent(
BOOL bInitiallyOwn
=
FALSE,
//
标识初始时是否有信号
BOOL bManualReset
=
FALSE,
//
标识是否为手动置位
LPCTSTR lpszNAme
=
NULL,
//
在进程间同步是可以指定名字
LPSECURITY_ATTRIBUTES lpsaAttribute
=
NULL
//
安全标识
);
创建所有的CEvent实例后,可以初始化CSyncObject数组,以供CMultiLock使用。
创建CMultiLock实例,指明同步的信号,构造函数:<afxmt.h>
CMultiLock::CMultiLock(
CSyncObject
*
ppObjects[],
//
同步对象数组
DWORD dwCount,
//
同步对象个数
BOOL bInitialLock
=
FALSE
//
标识初始时是否访问对象
);
CMultiLock可以应用于CEvent、CMutex、CCriticalSection。但对于CEvent并不存在对象锁定的概念,使用CMultiLock::Lock()方法可以等待CEvent实例激发,类似于WaitForMultipleObject()。注意微软已确认同步对象数组大小若大于8,则会出现内存泄漏(见参考文献)。
使用MFC同步对象代码示例:
//
自定义一个用于线程同步的类EventLock,并声明一些CEvent静态成员:
#define
EVNT_CNT 3
class
EventLock
{
public:
DWORD WaitForEvents();
void SetEvent(int iNum);
void ResetEvent(int iNum);
EventLock();
virtual ~EventLock();
static CEvent *pevnt[EVNT_CNT]; // EVNT_CNT标识事件对象数
private:
static CEvent evntClose, evntBegin, evntPause;
CMultiLock *pLock;
}
;

//
创建CEvent实例:
CEvent EventLock::evntClose (FALSE,TRUE);
CEvent EventLock::evntBegin (FALSE,TRUE);
CEvent EventLock::evntPause (FALSE);
//
初始化同步对象数组:
CEvent
*
EventLock::pevnt[]
=
{&evntClose,&evntBeginSample,
&evntPauseSample, &evntPreSample}
;

//
在构造器中创建CMultiLock实例:
EventLock::EventLock()
{
pLock = new CMultiLock((CSyncObject **)EventLock::pevnt,EVNT_CNT);
}
EventLock::
~
EventLock()
{
delete pLock;
}

//
与Event相同的MFC CEvent置位、复位操作:
void
EventLock::ResetEvent(
int
iNum )
{
ASSERT(0<=iNum && iNum<EVNT_CNT);
pevnt[iNum]->ResetEvent();
}
void
EventLock::SetEvent(
int
iNum)
{
ASSERT(0<=iNum && iNum<EVNT_CNT);
pevnt[iNum]->SetEvent();
}
//
使用CMultiLock::Lock()等待事件激发,此处为等待任意事件激发:
DWORD EventLock::WaitForEvents()
{
DWORD dwEvent = 0;
dwEvent = pLock->Lock(INFINITE, FALSE); // 第二个参数为TRUE则等待全部事件
dwEvent -= WAIT_OBJECT_0;
return dwEvent;
}
以上提到Windows CE下基本的多线程编程操作,可以满足小型项目的需要,但在具体开发中,要合理设计线程运行流程,防止并发运行中“与时间有关的错误”。
(文中源码均取自真实项目,在Mcrosoft embedded Visual C++ 4.0 SP4下编译通过,于ARM9+Windows CE4.2环境下运行正确稳定。)
Stone&Ice
参考文献:
1. 《Multithreading Applications in Win32》Jim Beveridge & Robert Wiener 侯捷译 书中详细讲述在Win32环境下进行多线程编程的方法及设计原则