Symbian 多线程使用详解

来源:互联网 发布:mac mysql可视化界面 编辑:程序博客网 时间:2024/05/18 01:24
《Symbian OS:线程编程》
1/14
《Symbian OS:线程编程》
《Symbian OS:线程编程》
2/14
目 录
0、Symbian 操作系统中的线程和进程.............................................. 3
1、使用单线程的优点........................................................... 3
2、使用多线程的优点........................................................... 3
3、线程的基本使用方法......................................................... 4
1)生成一个新线程............................................................................................................... 4
2)线程状态.......................................................................................................................... 4
3)访问线程及进程............................................................................................................... 5
4)线程优先级....................................................................................................................... 5
5)异常处理.......................................................................................................................... 7
6)其他线程函数................................................................................................................... 8
4、线程内部的通信............................................................. 9
1)共享内存.......................................................................................................................... 9
2)Client/Server API ........................................................................................................... 9
3)进程内数据传输............................................................................................................... 9
a)读取另个线程所提供的descriptor.............................................................................. 9
b)向另个线程写入descriptor.................................................................................. 10
c)Descriptor 帮助函数............................................................................................. 10
4)线程局部存储(TLS)..................................................................................................... 10
5) User-Interrupt Exception ................................................................................11
6) Publish & Subsribe .............................................................................................11
7) 消息队列......................................................................................................................11
5、同步...................................................................... 11
1)目的...............................................................................................................................11
2)使用Semaphores(信号) ............................................................................................ 12
3)使用互斥(Mutex)..................................................................................................... 12
4)使用临界区(Critical Sections).......................................................................... 12
5)同步实例....................................................................................................................... 13
6 总结........................................................................ 14
《Symbian OS:线程编程》
3/14
鉴于最近论坛上很多人都问到多线程的问题,hoolee 想将Nokia 今年(2005 年)三月刚
发布的技术文档《Symbian OS: Threads Programming》穇译给大家,希望能对大家有
所帮助。
虽然Symbian 操作系统中对多任务的实现更提倡使用活动对象,但多线程也是非常有用的技
术,当移植程序、后台大量复杂运算或多媒体编程时, threads 都是必不可少的。Symbian
中的thread 编程和一般的多线程编程差不多,下面就来看看具体文档中是如何描述的:
《Symbian OS:线程编程》
0、Symbian 操作系统中的线程和进程
在Symbian 操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程
的主线程是在进程启动时生成的。
Symbian 属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系统将
CPU 使用权给予其他线程。当系统调度时,具有最高优先权的线程将首先获得执行。
进程边界是受内存保护的。所有的用户进程都有自己的内存地址空间,同一进程中的所有线
程共享这一空间,用户进程不能直接访问其他进程的地址空间。
每个线程都有它自己的stack 和heap,这里heap 可以是私有的,也可以被其他线程共享。
应用程序框架生成并安装了一个active scheduler,并且为主线程准备了清除栈。如果没
有使用框架(如编写exe 程序)那就要手动生成这些了:)
Symbian 操作系统专为单线程应用优化,因此强烈推荐使用“活动对象”代替多线程。
1、使用单线程的优点
在每个线程都有自己的stack 空间时,使用单线程可以减少内存耗费。
在线程间切换上下文要比切换活动对象(通过active scheduler)慢得多。
不需要处理互斥现象,这减少了程序的负担,简化了代码,减少了错误发生的几率。
一些资源只能为主线程所用,因此它们并不是线程安全的,如动态数组。
2、使用多线程的优点
有时为了保证所执行的任务的持续性,如播放声音时,我们可以将其归在一个单独的线程中
处理。
将复杂的多线程或长时间运行程序移植到Symbian 上,如果不使用多线程处理,可能会比较
难也更耗时间。
(题外话:我曾将一个棋类程序移植到Symbian 上,里面复杂的递归运算使我不得不使用多
线程,这样的情况下,你是很难将时间有序的分化开来,使用活动对象的)
《Symbian OS:线程编程》
4/14
3、线程的基本使用方法
RThread 提供了线程的各项功能。线程是为内核所拥有的对象,RThread 对象封装了这些对
象的句柄。
1)生成一个新线程
新的线程可以通过构造一个RThread 对象,并调用它的Create()函数生成。如:
Code:
1: TInt threadFunction(TAny *aPtr)
2: {
3: // points to iParameter
4: TInt *i = (TInt *)aPtr;
5: ?_
6: }
7:
8: RThread thread;
9: thread.Create(KThreadName, threadFunction, 4096,
10: KMinHeapSize, 256*KMinHeapSize, &iParameter);
11: thread.Resume();
2)线程状态
一个线程的最重要的状态为运行、准备、等待和暂停。在生成后,线程首先处于暂停状态,
你可以调用Resume()函数来启动它的运行。一个线程也可以通过调用Suspend()来进入中
断状态。
线程一般通过Kill(TInt aReason)来结束,Terminate()与其相似。如果一个进程的主
线程结束,则该进程与所属所有线程都将结束。
一种非正常关闭线程的方式就是调用Panic(const TDesC& aCategory, TInt
aReason)来中断执行。
如何获得中断线程的信息呢, 我们可通过ExitType() , ExitReason() 以及
ExitCategory()方法来获得。
线程可以在中断时发出请求,我们通过调用异步方法Logon()来完成此任务。返回值在
aStatus 中。LogonCancel()可以取消前次请求。
void Logon(TRequestStatus& aStatus) const;
TInt LogonCancel(TRequestStatus& aStatus) const;
我们可以通过SetProtected(ETrue) 方法将线程保护起来, 也可以通过
《Symbian OS:线程编程》
5/14
SetProtected(EFalse)来取消保护。在保护时,另一个线程是无法中断、结束、异常中
断或设置该线程的优先级的。Protected()方法可以返回该线程的保护状态。
3)访问线程及进程
我们可以通过构造一个RThread 对象来访问当前线程。Id()方法可以返回该线程的ID。
拥有此线程的进程可以通过访问RThread 的Process(RProcess& aProcess)方法来获
得。这里传入的参数应该是一个RProcess 对象。
其他线程可以通过Open() 方法来访问。我们通过传递TThreadId 、线程名称或
TFindThread 对象来打开线程。
TInt Open(const TDesC& aFullName, TOwnerType aType=EOwnerProcess);
TInt Open(TThreadId aID, TOwnerType aType=EOwnerProcess);
TInt Open(const TFindThread& aFind, TOwnerType aType=EOwnerProcess);
Code:
1: // * as a wildcard for the name search
2: _LIT(KFindAll, “*”);
3: // default RThread object, has a handle of the current thread
4: RThread thread;
5: TFullName fullName;
6: TFindThread finder(KFindAll);
7:
8: while (finder.Next(fullName) == KErrNone)
9: {
10: // on success, variable ‘thread’ will contain a handle of
11: // a thread found by finder
12: thread.Open(finder);
13:
14: // get thread’s memory information
15: TInt heapSize, stackSize;
16: thread.GetRamSizes(heapSize, stackSize);
17:
18: // show fullName, heapSize and stackSize
19: ...
20: }
4)线程优先级
线程可以被赋予一个绝对或相对的优先级。绝对优先级定义了这个线程的总体优先级,不需
要考虑其拥有者进程的优先级了。而赋予相对优先级时则将此线称定义为拥有者进程的优先
《Symbian OS:线程编程》
6/14
级加上该相对优先级后的结果。
下面粗体标示的优先级值可以由用户代码设置:
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,
《Symbian OS:线程编程》
7/14
EPriorityMuchMore.
EPriorityNull 是一个特殊值,它定义了最低的级别,Kernel idle 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)异常类别及类型
异常类型是一组针对单个异常的类型识别,主要用在异常发生时。
异常类别则代表一组异常形式。
《Symbian OS:线程编程》
8/14
异常类别的一个集是由一个或多个异常类别通过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)
《Symbian OS:线程编程》
9/14
得到该线程中堆和栈的大小。
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 所指)。
《Symbian OS:线程编程》
10/14
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)线程局部存储(TLS)
Symbian 操作系统是不允许在DLL 中出现可写静态变量的。然而每个DLL 中每个线程都会
分配一个32 位字符空间。这个字符用来存放一个指向数据结构或类示例的指针。分配和释放
这些资源可在例如DLL 的入口函数E32Dll 中处理。
另一个使用线程局部存储的示例为保存指向类示例的指针,这样静态回调函数可以访问与线
程相联系的该对象。当我们处理自定义异常处理模块时是很有用的。
Dll::SetTls(TAny *aPtr)函数负责设置线程局部存储的指针。
Dll::Tls()函数负责返回一个指向线程局部存储的指针。取得后该指针所指定数据可以正
常使用。
《Symbian OS:线程编程》
11/14
5) User-Interrupt Exception
如3.5“Exception Handling”所述,线程可以引发其他线程的异常。有一种异常类型是
专为用户所保留的, 那就是EExcUserInterrupt , 可以通过指定异常 类型
KExceptionUserInterrupt 来处理。其他要传递的信息应该通过共享内存来处理。这是
在最段时间内向其他线程传递信息的方式,当异 常发生时调用RaiseException()函数可
切换到另个线程的异常处理模块。
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 是负责监听属性变化的线程。
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.
《Symbian OS:线程编程》
12/14
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()将会引发线程的异常。但不会出现在其他类
《Symbian OS:线程编程》
13/14
型的同步对象中。
线程的中断是不会影响critical sections 的状态的,因此使用critical sections
的线程将不会被其他线程杀死,除非不在critical sections 中。当不在需要时,线程的
死亡是不会有癬,很安全的。
5)同步实例
Code:
1: class CMessageBuffer
2: {
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();
《Symbian OS:线程编程》
14/14
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 和清除栈。总的来说,
线程编程不是这么容易的,因为这类编程需要全面理解框架、多任务和线程安全机制。
本文来自:
http://discussion.forum.nokia.com/forum/showthread.php?t=60374
(为方便阅读,由无名氏稍作整理而成。)
原创粉丝点击