Symbian OS:線程編程

hoolee想將Nokia今年三月剛發佈的技術文檔《Symbian OS: Threads Programming》穇譯給大家,希望能對大家有所幫助。
雖然symbian操作系統中對多任務的實現更提倡使用活動對象,但多線程也是非常有用的技術,當移植程序、後臺大量複雜運算或多媒體編程時,threads都是必不可少的。symbian中的thread編程和一般的多線程編程差不多,下面就來看看具體文檔中是如何描述的:

《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狀態)所註冊的上下文環境。

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 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中完成同步將使得該類成爲線程安全級,同樣它也可以被用在單個線程中了。

6總結
很多情況下都需要多線程的,當使用多線程時,同步及互斥排他也要考慮在內,以便保證線程通信的安全性。如果線程使用共享資源,我們應該使用某種同步機制來避免異常的發生,Semaphores, critical sections,和mutexes都提供了基本的解決方案。此外,如果使用活動對象或清除機制,我們還需要手工設置active scheduler和清除棧。總的來說,線程編程不是這麼容易的,因爲這類編程需要全面理解框架、多任務和線程安全機制。

(完)

BestRegards
hoolee

補充內容: 

A。使用RThread 創建多線程

// 定義線程句柄
RThread iThread;

// 創建線程
if (iThread.ExitType() == EExitKill)
 {
        iThread.Close();// 釋放句柄
 }
if (iThread.Create(_L("ThreadInvalidateMicro"), (TThreadFunction)InvalidateMicro,
        4096, KMinHeapSize, 256*KMinHeapSize, this) == KErrNone)
 {
        iThread.Resume(); 
}

// 終止線程,還有其他的終止方法
iThread.Kill(KErrNone);

// 線程調用的函數
static TInt InvalidateMicro(TAny *aPtr); // 聲明,必須爲靜態或全局

B。多次開線程需要線程名稱唯一:

TName name(KBinaryName);
// Randomness ensures a unique thread name
name.AppendNum(Math::Random(), EHex);

// Now create the server thread
const TInt KMinHeapSize=0x1000;
const TInt KMaxHeapSize=0x1000000;
RThread thread;
thread.Create(name, threadFunction, KDefaultStackSize, &starter, &lib, NULL, KMinHeapSize, KMaxHeapSize, EOwnerProcess);    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章