symbian 线程2

来源:互联网 发布:淘宝售后服务流程 换货 编辑:程序博客网 时间:2024/06/16 11:17
4、线程优先级
线程可以被赋予一个绝对或相对的优先级。绝对优先级定义了这个线程的总体优先级,不需要考虑其拥有者进程的优先级了。而赋予相对优先级时则将此线称定义为拥有者进程的优先级加上该相对优先级后的结果。

下面粗体标示的优先级值可以由用户代码设置:
Code:
enum TProcessPriority{   EPriorityLow=150,   EPriorityBackground=250,   EPriorityForeground=350,   EPriorityHigh=450,   EPriorityWindowServer=650,   EPriorityFileServer=750,   EPriorityRealTimeServer=850,   EPrioritySupervisor=950};enum TThreadPriority{EPriorityNull=(-30),EPriorityMuchLess=(-20),EPriorityLess=(-10),EPriorityNormal=0,EPriorityMore=10,EPriorityMuchMore=20,EPriorityRealTime=30,EPriorityAbsoluteVeryLow=100,EPriorityAbsoluteLow=200,EPriorityAbsoluteBackground=300,EPriorityAbsoluteForeground=400,EPriorityAbsoluteHigh=500};
上面枚举出来的值中绝对优先级值为:
EPriorityAbsoluteVeryLow, EPriorityAbsoluteLow, EPriorityAbsoluteBackground, EPriorityAbsoluteForeground, EPriorityAbsoluteHigh.
相对优先级值为:
EPriorityMuchLess, EPriorityLess, EPriorityNormal, EPriorityMore, EPriorityMuchMore.
EPriorityNull是一个特殊值,它定义了最低的级别,Kernel idel thread使用的就是它*_*

EPriorityRealTime定义了除核心服务线程优先级外最高的总体优先级。
RThread中的Priority()方法返回了一个线程的优先级(按以上描述值)。我们也可以通过SetPriority(TThreadPrioriy aPriority)方法来修改优先级。
ProcessPriority()方法返回了拥有该线程之进程的优先级(按TProcessPriority描述值)。我们也可以通过SetProcessPriority(TProcessPriority)方法来修改该进程的优先级。

5、异常处理
每个线程都有自己的异常处理模块。当线程发生异常时会调用异常处理模块。异常处理模块的訽型为:
typedef void TExceptionHandler(TExcType);

RThread包含了下列异常处理相关的方法:
TExceptionHandler* ExceptionHandler()
返回该线程当前异常处理模块的地址。

TInt SetExceptionHandler(TExceptionHandler* aHandler, TUint32 aMask);
定义了该线程新的异常处理模块的地址,以及它所处理异常的类别。

void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask)
修改异常处理模块所定之异常类别,aClearMask参数定义了不再为异常处理模块所处理的类别,而aSetMask则定义了新的处理类别。

TInt RaiseException(TExcType aType);
引发线程上指定类型的异常,这时异常处理模块将被启动执行(发生在调用之后)。

TBool IsExceptionHandled(TExcType aType);
检查线程的异常处理模块是否捕捉到aType类型的异常。


(1)异常类别及类型
异常类型是一组针对单个异常的类型识别,主要用在异常发生时。
异常类别则代表一组异常形式。

异常类别的一个集是由一个或多个异常类别通过OR形式组合成的,如KExceptionInteger|KExceptionDebug,这些值用来设置及修改异常处理模块所处理的类别。

下面列示了所有的类型及类别。
异常类别 异常类型
KExceptionInterrupt ->EExcGeneral, EExcUserInterrupt
KExceptionInteger ->EExcIntegerDivideByZero, EExcIntegerOverflow
KExceptionDebug->EExcSingleStep, EExcBreakPoint
KExceptionFault ->EExcBoundsCheck, EExcInvalidOpCode, EExcDoubleFault, EExcStackFault, EExcAccessViolation, EExcPrivInstruction, EExcAlignment, EExcPageFault
KExceptionFpe ->EExcFloatDenormal, EExcFloatDivideByZero, EExcFloatIndexactResult, EExcFloatInvalidOperation, EExcFloatOverflow, EExcFloatStackCheck, EExcFloatUnderflow
KExceptionAbort ->EExcAbort
KExceptionKill->EExcKill

6、其他线程函数
TInt Rename(const TDesC& aName)
为线程定义个新名字。

void RequestComplete(TRequestStatus*& aStatus, TInt aReason)
通知线程与一个异步请求绑定的请求状态对象aStatus已綺完成。sStatus完成代码将负责设置aReason及发出线程请求信号的通知。

TInt RequestCount()
返回线程请求信号的数目。如果是负值则表示该线程正在等待至少一个异常请求的完成。

void HandleCount(TInt& aProcessHandleCount, TInt& aThreadHandleCount)
得到线程中及拥有该线程的进程中处理模块的数目。

RHeap* Heap()
返回一个指向改线程堆的指针。

TInt GetRamSizes(TInt& aHeapSize, TInt& aStackSize)
得到该线程中堆和栈的大小。

TInt GetCpuTime(TTimeIntervalMicroSeconds& aCpuTime)
得到改线程所分配到的CPU时间

void Context(TDes8& aDes)
得到该线程( sleeping状态)所注册的上下文环境。

4、线程内部的通信
1)共享内存
在线程间交换信息最直接的方法就是使用共享内存。线程入口函数中有一个参数TAny* aPtr,这个指针可以用于任何目的。通常可以用它来传递一个负责线程间共享信息的数据结构或类实例。因为同一进程中的线程是共享内存地址空间的,因此这里指针所指向的数据可以被两个线程所共享,注意访问该数据时必须是同步形式。
另外这里的指针参数可以使用SetInitialParameter(TAny* aPtr)方法来改变,但这时线程应处于suspend状态。

2)Client/Server API
Symbian操作系统提供了一组基于server/session的API,允许一个线程扮演server的角色,向其他线程或进程提供服务。这里API也提供处理一组方法处理信息的传递,异步以数据传输。

3)进程内数据传输
如果两个线程分属不同的进程,则他们无法直接管理需要通信的数据,因为他们没有共享的数据区。这里可以使用RThread提供的ReadL()方法及WriteL()方法,我们可以用来在当前线程和由RThread提供的另一个线程间的地址空间拷贝8/16位的数据。这里当前线程和另一个线程可以归属同一个进程也可分属不同进程。

数据的传输是通过拷贝数据来完成的,RThread提供了方法返回在它地址空间内一个descriptor的长度及最大允许长度。

a>读取另个线程所提供的descriptor
void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const;
void ReadL(const TAny* aPtr,TDes16 &aDes,TInt anOffset) const;

这里ReadL()方法从另一个线程的descriptor(由aPtr所指)中拷贝一组数据,传递到当前线程的descriptor(由aDes所指)。
aPtr指针必须指向一个在RThread句柄所指线程的地址空间中有效的descriptor。

从源descriptor中的内容是从anOffset位置那里开始拷贝到目的descriptor(aDes)的。

b)向另个线程写入descriptor
void WriteL(const TAny* aPtr, const TDesC8& aDes, TInt anOffset) const;
void WriteL(const TAny* aPtr, const TDesC16& aDes, TInt anOffset) const;

用这个方法将当前线程descritor(aDes)所提供的数据都拷贝在另一个线程(aPtr所指)的descriptor中。这里anOffset参数设定了目标descriptor的初始化拷贝位置。

aPtr为线程地址空间内有效的可修改descriptor。

如果拷贝进去的数据长度超过目标descriptor的最大长度,则函数会发生异常。

c)Descriptor帮助函数
TInt GetDesLength(const TAny* aPtr) const;
TInt GetDesMaxLength(const TAny* aPtr) const;
这里RThread的GetDesLength()方法可以返回aPtr所指向的descriptor长度。这里descriptor必须为RThread句柄所指定的线程的地址空间中。
RThread的GetMaxDesLength()方法返回aPtr所指向descriptor的最大长度。descriptor也应在RThread句柄所指的线程地址空间中。

建议在ReadL()和WriteL()等方法前使用这些函数。

4.4线程局部存储(TLS)
Symbian操作系统是不允许在DLL中出现可写静态变量的。然而每个DLL中每个线程都会分配一个32位字符空间。这个字符用来存放一个指向数据结构或类示例的指针。分配和释放这些资源可在例如DLL的入口函数E32Dll中处理。

另一个使用线程局部存储的示例为保存指向类示例的指针,这样静态回调函数可以访问与线程相联系的该对象。当我们处理自定义异常处理模块时是很有用的。

Dll::SetTls(TAny *aPtr)函数负责设置线程局部存储的指针。
Dll::Tls()函数负责返回一个指向线程局部存储的指针。取得后该指针所指定数据可以正常使用。

4.5 User-Interrupt Exception
如3.5“Exception Handling”所述,线程可以引发其他线程的异常。有一种异常类型是专为用户所保留的,那就是EExcUserInterrupt,可以通过指定异常类型KExceptionUserInterrupt来处理。其他要传递的信息应该通过共享内存来处理。这是在最段时间内向其他线程传递信息的方式,当异常发生时调用RaiseException()函数可切换到另个线程的异常处理模块。

4.6 Publish & Subsribe
Publish & Subscrible是一个进程间的通信机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍),可以查看相关的文挡。

这个机制包括了三个基本方面:properties, publishers, 和subscribers.Properties是由一个标准SymbianOS UID所定义的全局唯一变量,它定义了属性类别,而另一个整数定义了property sub-key。
Publishers是负责更新属性的线程。Subscribers是负责监听属性变化的线程。

4.7 消息队列
消息队列是另一个进程间通信的机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍)。
消息队列用来向队列发送消息,而无需获得接收者的状态标识信息。任何进程(都在同一队列中的)或任何同一进程中的线程(在局部队列中)都可以读取这些信息。

5、同步
1)目的
如果多个线程在没有保护机制的情况下使用同一资源,就会出现一些问题。如,线程A更新了部分descriptor,而线程B接手后又重写了内容。回到线程A后,又开始更新内容。这样descriptor的内容就在A与B中来回修改了。

为了防止这类情况的发生,你需要使用非抢占式client/server机制或同步对象来处理。同步对象(mutex, semaphore, critical section)都是核心对象,可以通过句柄来访问。他们会限制或直接锁住对多线程们所要访问的资源,这种资源形式被称为共享资源。
在任何时刻只能有一个线程对共享资源进行写操作,每个要访问资源的线程都应使用同步机制来管理资源。
同步操作一般有如下步骤:
1. Call Wait() of the synchronization object reserved for this resource.
2. Access the shared resource.
3. Call Signal() of the synchronization object reserved for this resource.

注意,当kill线程时要小心点。因为如果线程使用已綺注销的对象,不同的同步对象其处理方式是不同的。因此,忽略使用同步类型而kill一个已綺更新过部分资源的线程是会引发问题的。

2)使用Semaphores(信号)
Semaphores可以管理共享资源的同步化访问。这里semaphore的句柄可通过RSemaphore类获得。
Semaphore限制了同一时刻访问共享资源的数目。semaphore计数的初始化工作可以放在构造函数中进行。
Semaphore可以是全局的也可以是局部的,全局的semaphore有自己的名称,可以被其他进程搜索并使用。而局部的semaphore没有名称,只能在同一进程间的线程中使用。
调用semaphore的Wait()方法将减少semaphore计数,而如果计数为负的话,调用线程就会进入等待状态。
调用semaphore的Signal()方法将增加semaphore计数,如果增长之前为负数,则等待信号的第一个线程将设定为准备运行状态。

调用semaphore的Signal(TInt aCount)和调用n次Signal()效果是一样的。
当线程死亡时,只有该线程正等待该信号时,信号才能被通知。因为信号在下面这样的情况也是可以执行的:在一个线程中调用Wait(),在另一个线程中调用Signal(),这样的信号无法在使用它的线程死亡时被通知。这样只会导致信号计数减低。

3)使用互斥(Mutex)
互斥主要使用在同步下独占访问共享资源。它的句柄可以通过RMutex类来获得。
和信号一样,互斥可以是全局也可以是局部的。唯一的不同在于其计数初始化时总为1。Mutex因此只允许最多一个访问共享资源。
如果一个线程已綺为mutex调用Wait(),但没有Signal(),则线程死亡时该互斥将被通知。

4)使用临界区(Critical Sections)
Critical Sections可用来在一单独进程中独占访问共享资源。Critical Sections句柄可以通过RCriticalSection类来获得。
Critical Sections只能用在同一进程的线程间,通常它用来管理某段代码的访问,每次只能有一个线程来访问。
同一线程中,在调用Wait()前调用Signale()将会引发线程的异常。但不会出现在其他类型的同步对象中。
线程的中断是不会影响critical sections的状态的,因此使用critical sections的线程将不会被其他线程杀死,除非不在critical sections中。当不在需要时,线程的死亡是不会有癬,很安全的。

5)同步实例
Code:
1: class CMessageBuffer2: {3: public:4: CMessageBuffer();5: void AddMessage(const TDes &aMsg);6: void GetMessages(TDes &aMsgs);7:8: public:9: RMutex iMutex;10: TDes iMsgs;11: };12:13: CMessageBuffer::CMessageBuffer()14: {15: iMutex.CreateLocal();16: }17:18: void CMessageBuffer::AddMessage(const TDes &aMsg)19: {20: iMutex.Wait();21: iMsgs.Append(aMsg);22: iMutex.Signal();23: }24:25: void CMessageBuffer::GetMessages(TDes &aMsgs)26: {27: iMutex.Wait();28: aMsg.Copy(iMsgs);29: iMsgs.Zero();30: iMutex.Signal();31: }32:33: static void CMyClass::threadFunction(TAny *aPtr)34: {35: CMessageBuffer *msgBuffer = (CMessageBuffer *)TAny;36: TInt count = 0;37: TBuf<40> msg;38:39: while (TRUE)40: {41: msg.Format(“ID: %d, count: %d/n”, RThread().Id(), count++);42: msgBuffer->AddMessage(msg);43: User::After(1000 * 1000);44: }45: }
在上面所述中,CMessageBuffer是一个半成品类,它允许用户增加消息到buffer中,也允许获得所有消息。
线程函数CMyClass::threadFunction负责向CMessageBuffer共享对象添加信息,这里内存分配和错误检查并没有列出,需要读者自己完成。
假设有多个线程要共享CMessageBuffer对象实例,则在实际访问buffer时必须要同步来处理。我们也可在线程函数中完成,但在CMessageBuffer中完成同步将使得该类成为线程安全级,同样它也可以被用在单个线程中了。

6总结
很多情况下都需要多线程的,当使用多线程时,同步及互斥排他也要考虑在内,以便保证线程通信的安全性。如果线程使用共享资源,我们应该使用某种同步机制来避免异常的发生,Semaphores, critical sections,和mutexes都提供了基本的解决方案。此外,如果使用活动对象或清除机制,我们还需要手工设置active scheduler和清除栈。总的来说,线程编程不是这么容易的,因为这类编程需要全面理解框架、多任务和线程安全机制。

(完)

BestRegards