多線程

多線程

  • 進程
    • 進程是指在系統中正在運行的一個應用程序
  • 線程
    • 一個進程想要執行任務,必須得有線程
    • 一個進程的所有任務都在線程中執行
    • 線程的串行
      • 一個線程中任務的執行時串行的,就是一個個的按順序執行
    • 多線程
      • 一個進程中可以開啟多條線程,每條線程可以并行(同時)執行不同的任務
      • 原理:
        • 同一時間,CPU只能處理一條線程,只有一條線程在工作
        • 多線程并發執行,其實是CPU快速地在多條線程之間調度
        • 如果CPU調度線程的速度夠快,就造成了多線程并發執行的家鄉
        • 如果線程過多,CPU在這么多線程之間調度,消耗大量的CPU資源
      • 優點:
        • 能適當提高程序的執行效率
        • 能適當提高資源利用率(CPU,內存利用率)
      • 缺點:
        • 創建線程是有開銷的,ios下主要成本包括:
          • 內核數據結構(大約1kb)
          • 棧空間(子線程512KB,主線程1MB,也可以使用-setStactSize:設置,但必須是4K的倍數,而且最小是16K)
          • 創建線程大約需要90毫秒的創建時間
        • 如果開啟大量的線程,會降低程序的性能
        • 線程越多,CPU在調度線程上的開銷就越大
        • 程序更加復雜:比如線程之間的通信,多線程的數據共享
  • IOS中多線程的實現方案
    • pthread
    • NSTread
    • GCD
    • NSOperation

NSTread

常用創建NSTread的方法:

方法1:

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject: (nullable id)argument;
/* 
特點:是快速創建一個線程并啟動它,讓它執行run:方法,Object可以傳入任何對象作為 run方法的參數.此方法無返回值,也就無法從這個方法拿到創建好的線程
*/
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"];

方法2:

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
/*
特點:初始化并返回一個NSThread,再給這個NSTread添加一個run方法,object:可以 傳run方法的參數,也可以傳nil,初始化完畢需要手動啟動線程。
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];

方法3:

- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
/*
特點:隱式的創建一個線程并啟動它這個方法所在聲明文件是@interface NSObject (NSThreadPerformAdditions),說 明只要繼承了NSObject類就可以使用,此方法無返回值。
*/
[self performSelectorInBackground:@selector(run:) object:@"test"]
獲取NSTread的方法:

獲取當前NSTread的方法:

+ (NSThread *)currentThread;NSThread *thread = [NSThread currentThread]; //獲取當前所在線程

獲取主線程的方法:

+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);NSThread *thread = [NSThread mainThread];
控制線程狀態的一些常用方法:

啟動當前線程對象方法

- (void)start NS_AVAILABLE(10_5, 2_0);
//創建一個線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];
//啟動這個線程
[thread start];

取消當前線程對象方法

- (void)cancel NS_AVAILABLE(10_5, 2_0);
//創建一個線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@ selector(run:) object:@"test"];
//啟動這個線程
[thread start];
//取消這個線程
[thread cancel];
注:如果是線程在執行一個循環操作,cancel取消不了這個操作.只能等循環結束

阻塞線程方法1:

NSDate *date = [NSDate date];
//這個方法返回一個表示當前時間的NSDate對象
date = [date dateByAddingTimeInterval:60]; 
//這個方法會在原本時間基礎上加60秒
[NSThread sleepUntilDate:date1]; 
//這個方法的作用是讓當前線程一直阻塞到時間為date1的時候,上面的例子的意思就是讓 當前線程阻塞到60秒后

阻塞線程方法2:

[NSThread sleepForTimeInterval:60]; 
//這個方法的作用是讓當前線程阻塞固定的秒數

退出當前線程的方法:

[NSThread exit]; 
//注:一旦此線程退出,就無法再啟動
查看線程狀態的一些常用方法:

查看當前線程優先級

double priority = [NSThread threadPriority]; 
//返回值是double,從0.0-1.0,1.0優先級最高

判斷當前所在線程是否為主線程

BOOL result = [NSThread isMainThread];
設置線程的一些方法

設置當前線程的優先級

BOOL result = [NSThread setThreadPriority:0.5]; 
//設置當前線程的優先級,設置成功會返回YES

檢測應用程序是否是多線程的方法

BOOL result = [NSThread isMultiThreaded];
/*
檢測應用程序是否多線程,返回值為YES時則程序為多線程 注:1.程序最開始只有一條主線程時調用此方法時,不管以后會有多少條線程,都會返回NO2.在所有非主線程都結束后,調用此方法還是會返回YES 
*/
NSTread的常用屬性

線程優先級屬性

property double threadPriority NS_AVAILABLE(10_6, 4_0);
property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
/*
NSQualityOfService的枚舉值優先級從高到低如下:
NSQualityOfServiceUserInteractive = 0x21 主要用于與UI交互的操作,各種事件處理以及繪制圖像等.
NSQualityOfServiceUserInitiated = 0x19 執行一些明確需要立即返回結果的任務.例如,用戶在郵件列表中選擇郵件后加載電子郵件
NSQualityOfServiceDefault = -1 默認
NSQualityOfServiceUtility = 0x11 用于執行不需要立即返回結果,耗時的操作,下載或者一些媒體操作等.
NSQualityOfServiceBackground = 0x09后臺執行一些用戶不需要知道的操作,它將以最有效的方式運行.例如一些與處理的操作,備份或者同步數據等等.*/

當前線程對象是否為主線程

property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);

當前線程對象是否正在執行任務

property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);

當前線程對象是否已執行完任務

@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);

當前線程對象是否被取消

@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);

線程的名稱

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
線程間通信的方法:

方法1:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/*
特點:
在子線程里調用這個方法意味著,將會回到主線程里去調用self的aSelector方法,arg處是給aSelector方法傳的參數 
注:wait參數是表示是否阻塞這個子線程,如果為YES,則要子線程要等待主線程執行完aSelector方法才會繼續往下執行。
聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
*/

方法2:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
//只比上個方法多了一個modes參數,是用來設置runLoop模式 
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)

方法3:

- (void)performSelector:(SEL)aSelector onThread:(NSThread )thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> )array NS_AVAILABLE(10_5, 2_0);
//只比上個方法多了一個onThread參數,意思就是可以從任意的兩個線程之間作轉換 
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)

方法4:

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject: (nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
//類似上面的方法,只少了設置runLoop模式的參數 
//聲明此方法的接口名稱是@interface NSObject (NSThreadPerformAdditions)
線程安全 –互斥鎖
  • synhronized(鎖對象){//需要鎖定的代碼}
    • 鎖對象只能是一個對象,不能是多個對象
    • 多條線程同時操作同一處數據的時候,就要加鎖
    • 缺點:互斥鎖需要消耗大量的CPU資源
    • 優點:防止多個對象同時操作同一數據,造成安全問題
    • 線程同步:多條線程在同一條線上按順序執行,互斥鎖就用到線程同步

GCD

簡介
  • 全稱是Grand Central Dispatch,可譯為“牛逼的中樞調度器”
  • 純C語言,提供了非常多強大的函數
  • 優勢:
    • GCD是蘋果公司為多核的并行運算提出的解決方案
    • GCD會自動利用更多的CPU內核
    • GCD會自動管理線程的生命周期(創建線程,調度任務,銷毀線程)
    • 程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼
  • 任務:執行什么操作
  • 隊列:用來存放任務
  • 使用步驟
    • 定制任務
    • 將任務添加到隊列中
      • GCD會自動將隊列中的任務取出,放到對應的線程中執行
      • 任務的取出遵循隊列的FIFO原則:先進先出,后進后出
兩個用來執行任務的函數
  • 同步
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
/*
queue:隊列
block:任務
*/
  • 異步
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • 同步和異步的區別

同步:只能在當前線程中執行任務,不具備開啟新線程的能力
異步:可以在新的線程中執行任務,具備開啟新線程的能力

隊列的類型
  • 并發隊列
    • 可以讓多個任務并發執行(自動開啟多個線程同時執行任務)
    • 并發功能只有在異步函數下才有效
    • GCD默認已經提供了全局的并發隊列,供整個應用使用
dispatch_get_global_queue(dispatch_queue_priority_t priority, // 隊列的優先級
unsigned long flags);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
// 獲得全局并發隊列全局并發隊列的優先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
  • 串行隊列
    • 讓任務一個接著一個地執行
    • GCD中獲得串行有2種途徑
方法1:
使用dispatch_queue_create函數創建串行隊列
dispatch_queue_create(const char *label, // 隊列名稱 
dispatch_queue_attr_t attr); // 隊列屬性,一般用NULL即可

dispatch_queue_t queue = dispatch_queue_create("myThread", NULL); // 創建
dispatch_release(queue); // 非ARC需要釋放手動創建的隊列
方法2:
使用主隊列(跟主線程相關聯的隊列)
* 主隊列是GCD自帶的一種特殊的串行隊列
* 放在主隊列中的任務,都會放到主線程中執行
* 使用dispatch_get_main_queue()獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
線程間的通信
  • 從子線程回到主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執行耗時的異步操作...
    dispatch_async(dispatch_get_main_queue(), ^{
    // 回到主線程,執行UI刷新操作
    });
});
  • 延時執行
方法1:調用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調用self的run方法方法2:使用GCD函數dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執行這里的代碼...
});
  • 一次性代碼
    使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行一次
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{
  // 只執行1次的代碼(這里面默認是線程安全的)
});
  • 隊列組
    當需要分別異步執行2個耗時的操作,等2個異步操作都執行完畢后,再回到主線程執行操作的時候,就可以用隊列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  // 等前面的異步操作都執行完畢后,回到主線程...
});

NSOperation

NSOperation和NSOperationQueue實現多線程的步驟
  • 先將要執行的操作封裝到一個NSOperation對象中
  • 然后將NSOperation對象添加到NSOperationQueue中
  • 系統會自動將NSOperationQueue中的NSOperation取出來
  • 將取出的NSOperation封裝的操作放到一條新線程中執行
NSOperation的子類
  • NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
  • 使用NSOperation子類的方式有三種
    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現內部相應的方法
NSInvocationOperation
//創建NSInvocationOperation對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
//調用start方法開始執行操作,一旦執行操作,就會調用target的sel方法
- (void)start;

注:默認情況下,調用了start方法后并不會開一條新線程去執行操作,注意是在當前線程同步執行操作,只有將NSOperation放到NSOperationQueue中才會異步執行操作,也就是說,在子線程調用這個方法不會跳到主線程執行任務,只會在這個子線程執行

NSBlockOperation
//創建NSBlockOperation對象
+ (id)blockOperationWithBlock:(void (^)(void))block;

//通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;

注意:只要NSBlockOperation封裝的操作數 > 1,就會異步執行操作

NSOperationQueue
  • NSOperationQueue的作用
    • NSOperation可以調用start方法來執行任務,但默認是同步執行的
    • 如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統會自動異步執行NSOperation中的操作
  • 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
  • 最大并發數相關的方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 隊列的取消,暫停,恢復
//取消隊列的所有操作
- (void)cancelAllOperations;
提示:也可以調用NSOperation的- (void)cancel方法取消單個操作
//暫停和恢復隊列
- (void)setSuspended:(BOOL)b; 
// YES代表暫停隊列,NO代表恢復隊列
- (BOOL)isSuspended;
  • 依賴操作
    • NSOperation之間可以設置依賴來保證執行順序
[operationB addDependency:operationA];
//操作B依賴于操作A
//可以在不同的queue的NSOperation之間創建依賴關系
  • 監聽操作
可以監聽一個操作的執行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
  • 自定義NSOperation的步驟
    • 重寫- (void)main方法,在里面實現想執行的任務
  • 重寫- (void)main方法的注意點
    • 自己創建自動釋放池(因為如果異步操作,無法訪問主線程的自動釋放池)
    • 通常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容