Objective-C編程 — 並行編程

来源:https://www.cnblogs.com/Julday/archive/2020/02/28/12376858.html
-Advertisement-
Play Games

多線程 線程的基本概念 線程 (thread)是進程(process)A 內假想的持有 CPU 使用權的執行單位。一般情況下,一個進程 只有一個線程,但也可以創建多個線程併在進程中並行執行。應用在執行某一處理的同時,還可以 接收 GUI 的輸入。 使用多線程的程式稱為 多線程 (multithrea ...


多線程

線程的基本概念

線程 (thread)是進程(process)A 內假想的持有 CPU 使用權的執行單位。一般情況下,一個進程 只有一個線程,但也可以創建多個線程併在進程中並行執行。應用在執行某一處理的同時,還可以 接收 GUI 的輸入。

使用多線程的程式稱為 多線程 (multithread)運行。從程式開始執行時就運行的線程稱為 主線程 , 除此之外,之後生成的線程稱為次線程(secondary thread)或子線程(subthread)。

創建線程時,創建方的線程為父線程,被創建方的線程為子線程。父線程和子線程並行執行各
自的處理,但父線程可以等到子線程執行終止後與其會合(join)。而另一方面,線上程被創建後, 也可以切斷父子關係指定它們不進行會合。該操作稱為 分離 (detach)。這裡所說的 NSThread 就是在 分離狀態下創建線程。

一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:1012951431, 分享BAT,阿裡面試題、面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路。

由於被創建的線程共用進程的地址空間,所以能夠自由訪問進程的空間變數。多線程訪問的變數稱為 共用變數 (shared variable) 。共用變數大多為全局變數或靜態變數,但因為地址空間是共用的, 所以理論上所有記憶體區域都可以稱為共用變數。

如果多線程胡亂訪問共用變數,那麼就不能保證變數值的正確性。所以有時就需要按照一定的 規則使多線程可以協調動作。此時就必須執行線程間 互斥 (或者排他控制,mutual exclusion)(見 。 各個線程都分配有棧且獨立進行管理。基本上不能訪問其他線程的棧內的變數(自動變數)。通 過遵守這樣的編程方式,就可以自由訪問方法或函數的自動變數,而且不用擔心互斥。

使用引用計數管理方式時,為了使對象之間解耦合,子線程方需要創建與父線程不同的自動釋
放池來管理。使用垃圾回收時不需要這樣。

A 任務(task)這一名稱也被用來表示與進程同樣的概念,在蘋果公司的文檔“Multithreading Programming Topics”中, 可以包含多線程的程式的執行單元稱為進程,而任務則被用來抽象地表示應該進行的作業。

線程安全

多個線程同時操作某個實例時,如果沒有得到錯誤結果或實例沒有包含不正確的狀態,那麼該 類就稱為 線程安全 (thread-safe)。結果不能保證時,則稱為非線程安全或線程不安全(thread-unsafe)。

一般情況下,常數對象是線程安全的,變數對象不是線程安全的。常數對象可以線上程間安全
地傳遞,但對變數對象共用時,需要恰當地執行互斥或同步切換。

需要註意的是 C 語言的函數。就現狀來看,BSD 函數的大部分,例如 printf() 等,都不是線程安 全的。

註意點

在某些情況下,使用多線程可以使處理高速化、實現易於使用的介面、使實現更簡單等。但並 不是說使用多線程後就一定會得到這些優點。

要想使多線程程式不出錯且高效執行,並行編程的知識必不可少。線程間的任務分配和信息交 換、共用資源的互斥、與 GUI 的交互及動畫顯示等,在使用時都要特別小心。

一般情況下,自己實現多線程程式是很困難的,而且也容易埋下高隱患。稍有差錯或設計失誤,
多 線 程 便 不 能 發 揮 效 果, 甚 至 還 會 導 致 未 知 原 因 的 釋 放 或 異 常 終 止。 使 用 19.3 節 中 介 紹 的 NSOperation,雖然可以較容易地實現多線程程式,但是也必須掌握線程動作、互斥等相關知識。不 能適應這些的讀者建議去參考一下並行編程的相關書籍。

而且,很多多線程中遇見的問題都可以通過 NSTimer 類或延遲消息發送(參考 15.1 節)來解決。 大家也不妨嘗試一下用這些方法來解決相關問題。

使用 NSThread創建線程

Foundation 框架中提供了 NSThread 類來創建並控制線程。該類的介面在 Foundation/NSThread.h 中聲明。

創建新線程需要執行下麵的類方法。

+ (void) detachNewThreadSelector: (SEL) aSelector

toTarget: (id) aTarget

withObject: (id) anArgument

對 對象 aTarget 調用方法創建新線程並執行。選擇器 aSelector 必須是僅獲取一個 id 類型參數且返回值 為 void 的執行方法(參考 8.2 節)。

指定的方法執行結束後,線程也隨之終止。線程從最初就被執行了分離,所以終止時沒有和父 線程會合。當主線程終止時,包含子線程的程式也全部隨之終止。

使用引用計數管理(手動及 ARC)時,有時需要執行的方法自身來管理自動釋放池。此外,參 數 aTarget 和 anArgument 中指定的對象也與線程同時存在,即在創建線程時被保存,線上程終止時 被釋放。 使用下述的 NSApplication 類中的方法也能創建線程。該方法使用上面的方法,而且在使用引用 計數管理時還會創建線程的自動釋放池。

+ (void) detachDrawingThread: (SEL) selector

toTarget: (id) target

withObject: (id) argument

創建新線程並執行的方法除了上述方法還有很多,本書中不再一一介紹。其他方法請參考 NSThread、NSObject 的參考文檔。

程式可以調用 NSThread 類方法來確認是否是多線程運行。 + (BOOL) isMultiThreaded   多個線程並行執行時或者只有主線程在執行時,只要在此之前已經創建了線程,則返回 YES。

當前線程

一個線程稱自身為 當前線程 (current thread),區別於其他線程。

子線程將創建時指定的方法執行完後也會隨之終止,但也可以中途終止。為此,可以使用當前 線程(線程自身)來執行下一個 NSThread 類方法。但是,使用引用計數管理時,終止前一定要釋放 自動釋放池。 + (void) exit

使用下述方法獲得表示線程的 NSThread 實例。

+ (NSThread *) currentThread

獲 得表示當前線程的 NSThread 實例。

+ (NSThread *) mainThread

獲 得表示主線程的NSThread實例。查看當前線程是否為主線程時,可以使用類方法isMainThread 。 每個線程都可以持有一個該線程固有的 NSMutableDictionary 類型的字典。向 NSThread 實例發 送下麵的消息類就可以取得字典。

- (NSMutableDictionary *) threadDictionary

可以使當前線程僅被中斷幾秒。為此,可在當前線程中執行下麵的類方法。參數為實數。 + (void) sleepForTimeInterval: (NSTimeInterval) ti

也可以使線程在某一時刻前中斷,這時可採用下麵的類方法。參數是表示日期的類 NSDate 實例。 + (void) sleepUntilDate:(NSDate *) aDate

如果要使線程到某個條件成立前一直保持休眠狀態,則要使用下一章節介紹的鎖。

GUI應用和線程

在使用 GUI 的應用中,事件處理和繪圖等大部分處理中線程都發揮了重要作用。也可以在子線 程中創建窗體,或分擔部分繪圖功能,但要註意避免競爭或記憶體泄漏。詳情請參考相關文檔。

GUI 應用中有較容易的方法來使用線程,即將 GUI 相關的時間處理或繪圖集中在主線程中進行。
使用下麵的方法,就可以從子線程依賴主線程中的方法處理。該方法為 NSOjbect 的範疇,在頭文件 Foundation/NSThread.h 中聲明。

- (void) performSelectorOnMainThread: (SEL) aSelector

withObject: (id) arg

waitUntilDone: (BOOL) wait

選 擇器 aSelector 和參數 arg 中指定的方法的執行依賴於主線程。wait 為 YES 時,當前線程會一直等待 至執行結束。主線程中必須有事件迴圈(運行迴路)。

互斥

需要互斥的例子

在多線程環境中,無論哪個函數或方法都可以在多線程中同時執行。但是,在使用共用變數時, 或者在執行文件輸出或繪圖等的情況下,多線程同時執行就可能得到奇怪的結果。

例如,使用整數全局變數 totalNumber 來累加所處理的數據的個數。為了執行下麵的加法計算, 

在多線程環境中執行該方法會得到什麼結果呢?

- (void)addNumber:(NSIngeger)n

{

totalNumber += n;

} 在 OS 功能支持下,線程在運行的過程中會時而得到 CPU 的執行權,時而被掛起執行權,2 個 方法的執行情況如圖 19-1 中所示。在該圖中,線程 1 將新計算的值保存在寄存器時掛起 CPU 執行 權,同時線程 2 開始執行方法。即使 CPU 的執行權被掛起,寄存器的值也仍然可以被保存,所以各 線程都能正常處理。但是,由於線程 2 寫入的值消失了,因此整體上看,這偏離了我們期待的結果。 原因是值的讀取、更新、寫入操作被多線程同時執行了。

 

在圖 19-1 的例子中,我們將同時只可以由一個線程占有並執行的代碼部分稱為臨界區(critical section),或稱為危險區。互斥的目的就是限制可以在臨界區執行的線程。

為了使多個線程間可以相互排斥地使用全局變數等共用資源,可以使用NSLock 類。該類的實例 也就是可以調整多線程行為的 信號量 (semaphore)或者 互斥型信號量 (mutual exclusion semaphore)。 Cocoa 環境中也稱為 鎖 (lock)。

鎖具有每次只允許單個線程獲得並使用的性質。獲得鎖稱為“加鎖”,釋放鎖稱為“解鎖”。

鎖和普通的實例一樣,使用類方法alloc 和初始化器init 來創建並初始化。但是,鎖應該在程 序開始在多線程執行前創建。

NSLock *countLock = [[NSLock alloc] init];

獲得鎖的方法和釋放(unlock)鎖的方法都在協議 NSLocking 中定義。

- (void) lock   如果鎖正被使用,則線程進入休眠狀態。

如果鎖沒有被使用,則將鎖的狀態變為正被使用,線程繼續執行。

- (void) unlock   將 鎖置為沒有在被使用,此時如果有等待該鎖資源的正在休眠的線程,則將其喚醒。

在上例中,使用鎖後會產生如下效果。但需要預先創建 NSLock 的實例 aLock。在該代碼中,從 某線程執行 A 取得鎖到該線程執行 B 釋放鎖期間,其他線程在執行 A 時將進入休眠狀態,不能執 行臨界區代碼。鎖被釋放後,在執行 A 時休眠的線程中選擇一個線程,該線程在取得鎖後進入臨界 區執行。

- (void)addNumber:(NSIngeger)n {     [aLock lock];  ─────────────────────────────────────────  A     totalNumber += n;    // 臨界區     [aLock unlock]; ────────────────────────────────────────  B }

某個鎖被lock 後,必須執行一次unlock 。而且lock 和unlock 必須在同一個線程執行 A。

下麵來看另外一個使用鎖的例子。考慮一下全局變數值自增時返回其結果的方法。多線程執行 時,全局變數 theCount 若想正確地自增,就需要使用鎖 countLock 來管理。

可以採用如下定義。 

 

A lock 和 unlock 必須在同一個線程中執行,因為 NSLock 是基於 POSIX 線程實現的。

死鎖

線程和鎖的關係必須在設計之初就經過仔細的考慮。如果錯誤地使用鎖,不但不能按照預期執 行互斥,還可能使多個線程陷入到不能執行的狀態,即死鎖(deadlock)狀態。

死鎖就是多線程(或進程)永遠在等待一個不可能實現的條件而無法繼續執行,如圖 19-2 所示。 

 

線程 1 占有文件 A 並正在進行處理,途中又需要占有文件 B。而另一方面,線程 2 占有著文件 B,途中又需要占有文件 A。大家不妨設想一下,如果線程 1 和線程 2 同時執行到了圖中的箭頭位置 會怎麼樣呢?線程 1 為了處理文件 B 想要獲得鎖 lockForB,但是它已經被線程 2 獲得。同樣,線程 2 想要獲得的鎖 lockForA 也被線程 1 占有著。這種情況下,線程 1 和線程 2 就會同時進入休眠狀態, 而且雙方都不能跳出該狀態。

像這樣,當多個線程互相等待資源的釋放時,就非常容易出現死鎖現象。有時是多個線程相干預,有時則是一個線程因為自己需要獲得鎖而進入休眠狀態。此外,由於多數情況下各個線程本身 並沒有錯誤處理,而且死鎖又隨時可能發生,因此追究原因就非常困難,也不能排除導致程式 bug 的可能。

嘗試獲得鎖

NSLock 類不僅能獲得鎖和釋放鎖,還有檢查是否能獲得鎖的功能。利用這些功能,就可以在不 能獲得鎖時進行其他處理。

- (BOOL) tryLock

用 接收器嘗試獲得某個鎖,如果可以取得該鎖則返回 YES。不能獲得時,與lock 處理不同,線程沒 有進入休眠狀態,而是直接返回 NO 並繼續執行。

該方法十分便利,但要確保只能在可以獲得鎖時才執行 unlock,創建程式時必須註意這一點。

條件鎖

類 NSConditionLock 稱為 條件鎖 (condition lock)。該鎖持有整數值,根據該值可以獲得鎖或者 等待。

  • (id) initWithCondition: (NSInteger) condition

NSConditionLock 實例初始化,設置參數 condition 指定的值。

NSCondtionLock 的指定初始化器。

  • (NSInteger) condition

此時返回鎖中設定的值。

  • (void) lockWhenCondition: (NSInteger) condition

如果鎖正在被使用,則線程進入休眠狀態。

鎖不在被使用時,如果鎖值和參數 condition 的值一致,則將鎖狀態修改為正在被使用,然後繼續執 行,如果不一致,則線程進入休眠狀態。

  • (void) unlockWithCondition: (NSInteger) condition

在鎖中設置參數 condition 指定的值。將鎖設置為不在被使用,此時如果有等待獲得該鎖且處於休眠 狀態的線程,則將其喚醒。

  • (BOOL) tryLockWhenCondition: (NSInteger) condition

尚未使用鎖且鎖值與參數 condition 相同時,獲得鎖並返回 YES。不能獲得鎖時也不進入休眠狀態, 而是返回 NO,線程繼續執行。

使用方法 lock 、 unlock 或 tryLock 都可以獲得鎖和釋放鎖,而且無需關心鎖的值。

然而,由於 NSConditionLock 實例可以持有的狀態為整數型,所以事先用枚舉常數或巨集定義就可 以了。如果只使用 0 或 1,不僅不容易理解,也可能造成錯誤。

NSRecursiveLock

某線程獲得鎖後,到該線程釋放鎖期間,想要獲得該鎖的線程就會進入休眠。使用類 NSLock 的 鎖時,如果已經獲得鎖的線程在沒有釋放它的情況下還想再次獲得該鎖,該線程也會進入休眠狀態。 但是,由於沒有從休眠狀態喚醒的線程,所以這就是死鎖。下麵是一個簡單的例子,這段代碼不會 執行。

[aLock lock];

[aLock lock];      // 這裡發生死鎖

[aLock unlock];

[aLock unlock];

解決這種情況可以使用 NSRecursiveLock 類的鎖,擁有鎖的線程即使多次獲得同一個鎖也不會 進入死鎖。但是,其他線程當然也不能獲得該鎖。獲得次數和釋放次數一致時,鎖就會被釋放。

NSRecursiveLock 類的鎖使用起來十分方便,但排除被重覆加鎖的情況,用 NSLock 來重新記述 
的話,性能則會更好。

@synchronized

程式內的塊可以指定為不被多線程同時使用。為此可以使用 @synchronized 編譯符,如下所示。

 

通過使用該段代碼,運行時系統就可以創建排斥地執行該代碼塊的鎖(mutex)。參數 obj 通常指 定為該互斥鎖要保護的對象。obj 自己不需要是鎖對象。

線程如果要執行該代碼塊,首先會嘗試獲得鎖,如果能獲得鎖則可以執行塊內代碼。塊執行結 束時一併釋放鎖。使用 break 或 return 從塊內跳出到塊外時也被視作塊執行終止。而且,在塊內發生 異常時,運行時系統會捕捉異常並釋放塊。

@synchronized 的參數對象決定對應的塊。所以, 同一個對象參數的 @synchronized 塊如果有多 個,則不可以同時執行。

根據參數的選擇方法的不同,@synchronized 會在並行執行的受限對象和可以執行的普通對象之 間動態切換。下麵展示 @synchronized 參數的使用示例。

(a) 是指定只能單獨存在的對象時的情景。同一個對象在其他地方也作為 @synchronized 的參數 使用時,所有這些塊不能同時執行。(b) 也是一樣,因為限制了參數的使用範圍,互斥對象顯然只能 是該方法內的塊。

(c) 是各個實例互斥的例子。一個實例一次只能執行一個線程,同一類別的其他實例則多個線程可以同時存在。(d) 在參數對象可能在多個地方更改的情況下有效,但以同樣方式使用該對象的所有 場所中都需要按照該方式書寫,否則就沒有任何意義。

而且,也可以按照 (e) 的方式書寫。此外還可以指定類對象,或者使用消息選擇器(隱藏參數的 _cmd)來指定方法等。不過一般情況下,為互斥的對象使用專門的鎖對象是比較可靠的方法。

 

使用 @synchronized 塊時,加鎖和解鎖必須成對進行,因此可以防止加鎖後忘記解鎖這種問題的 發生。和普通的鎖相比,複雜的並行演算法的書寫會較為複雜,但多數情況下都會使互斥更容易理解。
另外,如果你想一起進階,不妨添加一下交流群1012951431,選擇加入一起交流,一起學習。期待你的加入!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面的系列文章基本講完了linux管理相關的基礎知識,從本篇開始講解centos7中服務程式的部署和配置,以便為外部提供各種服務。 日常工作和娛樂中,我們所需的各種資源都離不開網路以及各種服務,我們通過網路獲取部署在其他伺服器上的各種服務資源,這些服務包括文件服務、郵件服務、媒體服務等等。 一般情況 ...
  • 1 Jprofile簡介 "官網" 下載對應的系統版本即可 性能查看工具JProfiler,可用於查看java執行效率,查看線程狀態,查看記憶體占用與記憶體對象,還可以分析dump日誌. 2 功能簡介 選擇attach to a locally running jvm 選擇需要查看運行的jvm,雙擊或者 ...
  • 一、NoSQL和關係型資料庫區別 NoSQL非關係型資料庫:Redis、MongoDB、HBase等,基於Key-Value存儲,採用命令操作。 關係型資料庫:Oracle、MySQL、DB2、SQL Server等,基於表結構存儲,採用SQL操作。 二、Redis簡介 Redis是由義大利人Sal ...
  • 前言 曾幾何時,看著高大上的架構和各位前輩高超的炫技,有沒有怦然心動,也想一窺究竟?每當面試的時候,拿著單應用的架構,吹著分庫分表的牛X,有沒有心裡慌的一批? 其實很多時候,我們所缺少的只是對高大上的技術的演練。沒有相關的業務需求,沒有集群環境,然後便只是Google幾篇博文,看下原理,便算是瞭解了 ...
  • SQLyog安裝教程 一、軟體下載 為了更好的學習,我們需要可視化界面,而不僅僅是通過命令行黑視窗管理資料庫。SQLyog 就是一個快速而簡潔的圖形化管理MYSQL資料庫的工具。 SQLyog12.08下載地址: 鏈接:https://pan.baidu.com/s/1iU6NtsthYwgx02z ...
  • Installation did not succeed. The application could not be installed: INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME Installation failed due to: 'null' ...
  • 註意:無特殊說明,Flutter版本及Dart版本如下: Flutter版本: 1.12.13+hotfix.5 Dart版本: 2.7.0 BottomNavigationBar 和 BottomNavigationBarItem配合Scaffold控制項使用可以實現底部導航效果,類似於微信底部的導 ...
  • 屬於個人所創,轉載請標明文章出處: https:////www.cnblogs.com/tangZH/p/12356915.html http://77blogs.com/?p=211 背景不多說,反正ndk載入gif比java上載入gif好很多很多,主要體現在記憶體占用與cpu消耗上。使用ndk載入 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...