《Symbian OS:线程编程》
Symbian操作系统中的线程和进程

在Symbian操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程的主线程是在进程启动时生成的。
Symbian属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系统将CPU使用权给予其他线程。当系统调度时,具有最高优先权的线程将首先获得执行。
进程边界是受内存保护的。所有的用户进程都有自己的内存地址空间,同一进程中的所有线程共享这一空间,用户进程不能直接访问其他进程的地址空间。
每个线程都有它自己的stack和heap,这里heap可以是私有的,也可以被其他线程共享。应用程序框架生成并安装了一个active scheduler,并且为主线程准备了清除栈。如果没有使用框架(如编写exe程序)那就要手动生成这些了:)
Symbian操作系统专为单线程应用优化,因此强烈推荐使用“活动对象”代替多线程。

一、使用单线程的优点
在每个线程都有自己的stack空间时,使用单线程可以减少内存耗费。
在线程间切换上下文要比切换活动对象(通过active scheduler)慢得多。
不需要处理互斥现象,这减少了程序的负担,简化了代码,减少了错误发生的几率。
一些资源只能为主线程所用,因此它们并不是线程安全的,如动态数组。

二、使用多线程的优点
有时为了保证所执行的任务的持续性,如播放声音时,我们可以将其归在一个单独的线程中处理。
将复杂的多线程或长时间运行程序移植到Symbian上,如果不使用多线程处理,可能会比较难也更耗时间。
(题外话:我曾綺将一个棋类程序移植到symbian上,里面复杂的递归运算使我不得不使用多线程,这样的情况下,你是很难将时间有序的分化开来,使用活动对象的)

三、线程的基本使用方法
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)方法将线程保护起来,也可以通过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、线程优先级
线程可以被赋予一个绝对或相对的优先级。绝对优先级定义了这个线程的总体优先级,不需要考虑其拥有者进程的优先级了。而赋予相对优先级时则将此线称定义为拥有者进程的优先级加上该相对优先级后的结果。

下面粗体标示的优先级值可以由用户代码设置:

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状态)所注册的上下文环境。

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

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

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

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中有所介绍)。
消息队列用来向队列发送消息,而无需获得接收者的状态标识信息。任何进程(都在同一队列中的)或任何同一进程中的线程(在局部队列中)都可以读取这些信息。

五、同步
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 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();
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中完成同步将使得该类成为线程安全级,同样它也可以被用在单个线程中了。

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

转自  http://hi.baidu.com/android_soft/blog/item/82686eebca3235dad439c981.html

Symbian--操作系统中的线程和进程相关推荐

  1. Symbian操作系统中的线程和进程

    在Symbian操作系统中,每个进程都有一个或多个线程.线程是执行的基本单位.一个进程的主线程是在进程启动时生成的. Symbian属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系 ...

  2. java中的线程和进程,Java | 线程和进程,创建线程

    一.线程与进程 线程定义 进程中执行的一个代码段,来完成不同的任务 组成:线程ID,当前指令指针(PC),寄存器集合(存储一部分正在执行线程的处理器状态的值)和堆栈 进程定义 执行的一段程序,一旦程序 ...

  3. Python中的线程、进程、协程以及区别

    进程,是执行中的计算机程序.也就是说,每个代码在执行的时候,首先本身即是一个进程.一个进程具有:就绪,运行,中断,僵死,结束等状态(不同操作系统不一样). 运行中每个进程都拥有自己的地址空间.内存.数 ...

  4. 解析Python中的线程与进程

    1 基础知识 现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率. 1.1 线程 线程是一个基本的 CPU 执行单元.它必须依托于进程存活.一个线程是一个execution ...

  5. 操作系统多线程实现_操作系统中的线程实现

    操作系统多线程实现 Each process has an address space. There is one thread of control in every traditional OS. ...

  6. 操作系统课程设计-线程和进程的同步与互斥

    操作系统的课程设计 多线程和多进程同步方法解决水果分配问题: 水果分配的问题: 桌上有一只盘子,每次只能放入5只水果.爸爸专放苹果, 妈妈专放橘子,一个儿子专等吃盘子中的橘子,一个女儿专等吃盘子中的苹 ...

  7. java线程和进程的区别_Java中的线程和进程比较

    1.进程和线程的区别 通俗一点说,进程就是程序的一次执行,而线程可以理解为进程中的执行的一段程序片段. 用一点文词说就是,每个进程都有独立的代码和数据空间(进程上下文);而线程可以看成是轻量级的进程. ...

  8. python中的线程和进程。

    1.什么是进程和线程. 1.1 进程的概念 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,每个进程都有自己的地址空间. ...

  9. PA 操作系统中的线程

    我是目录 批处理系统 内核线程与用户线程 关于线程 批处理系统 riscv 指令集提供了ecall指令,当cpu执行到这个ecall指令后,会跳转到mtvec这个寄存器指向的地址执行操作系统的代码,我 ...

最新文章

  1. 五子棋c语言策划书活动内容,五子棋比赛策划书
  2. 几个经常需要自定义的View总结
  3. jQuery的无new构建
  4. LeetCode 1019. 链表中的下一个更大节点(单调栈)
  5. dlib 怎么安装vs2017_win10中的dlib库安装过程
  6. P2911 [USACO08OCT]Bovine Bones G (python3实现)
  7. pythonargmaxaxis1_np.argmax(input,axis)和tf.argmax(input,axis)使用
  8. java获取泛型的类型_Java反射获取泛型类型
  9. “再见,Linux!”
  10. Rust 升级成微软第一梯队语言;“熊孩子”乱敲键盘攻破 Linux 桌面;500 个值得学习的 AI 开源项目| 开发者周刊...
  11. c语言实验操作期末考试怎么把试题保存,c语言上机操作练习题_相关文章专题_写写帮文库...
  12. win10更换系统启动时候的图片
  13. echarts 动态设置y轴单位_Recharts动态设置y轴的最大值最小值
  14. python旋转校正原理_Python+OpenCV实现旋转文本校正方式
  15. FMS直播和点播测试
  16. 七种方式实现三栏布局
  17. 【mysql】mysql表分区、索引的性能测试
  18. Android听筒模式和免提模式的切换
  19. 利用java swing编写一个简易的计算器,实现了括号,优先级,三角函数,阶乘等功能
  20. 交个朋友,一个普通二本物联网程序员的自述

热门文章

  1. 发现一款在线制图,思维导图软件ProcessOn
  2. python读取xlsx文件pandas_Python使用pandas读取xlsx文件,python
  3. 二进制与十进制的转换【相互转换, C++】
  4. 智能网联汽车测试场设计技术要求
  5. HITICSAPP2019大作业——程序人生-Hello’s P2P
  6. 浙江大学陈越教授数据结构PTA 题目——最大子列和(在线处理法)
  7. 关于南方cass7.0
  8. 2019年华北五省计算机应用技术大赛,参赛总结(参赛项目:简说APP)
  9. 【LiteOS】LiteOS移植常见问题
  10. 2021年最新Chrome、Firefox等浏览器网页端播放海康威视、大华RTSP视频流方案大汇总