進程:系統中正在運行的一個程序,進程之間是相互獨立的,每個進程都有屬於自己的內存空間。比如手機中的 微信 應用和 印象筆記 應用,他們都是iOS系統中獨立的進程,有着自己的內存空間。
線程:進程內部的任務執行路徑,可簡單理解爲 一個程序它是按順序從上往下執行的, 這個執行順序我們可以把它看成是一條線,把這條線就叫做線程。進程若想執行任務,則必須得在線程下執行。也就是說進程至少有一個線程才能執行任務。
多線程的實現原理:雖然在同一時刻,CPU只能處理1條線程,但是CPU可以快速地在多條線程之間調度(切換),達到多線程併發的近似效果。
多線程的優點:
- 能適當提高程序的執行效率
- 能適當提高資源利用率(CPU、內存利用率)。
多線程的缺點:
- 創建線程是需要成本的:在棧空間的子線程512KB、主線程1MB,創建線程大約需要90毫秒的創建時間。
- 線程越多,CPU在調度線程上的開銷就越大。
- 線程越多,程序設計就越複雜:因爲要考慮到線程之間的通信,多線程的數據共享。
iOS主線程:刷新UI,響應UI事件
iOS子線程:異步執行,不影響主線程,處理耗時任務,如網絡請求,加載圖片,複雜運算
多線程安全:當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題。就好比好幾個人在同時修改同一個表格,造成數據的錯亂。
多線程資源搶奪的解決方案
要給數據添加互斥鎖 。也就是說,當某線程訪問一個數據之前就要給數據加鎖,讓其不被其他的線程所修改。就好比一個人修改表格的時候給表格設置了密碼,那麼其他人就無法訪問文件了。當他修改文件之後,再講密碼撤銷,第二個人就可以訪問該文件了。
這裏的線程都爲子線程,如果給數據加了鎖,就等於將這些異步的子線程變成同步的了,這也叫做線程同步技術。
- atomic:原子屬性,爲setter方法加鎖(默認就是atomic)
- nonatomic:非原子屬性,不會爲setter方法加鎖
多線程在iOS中的應用:GCD
GCD的優勢:
- 自動利用更多的CPU內核(比如雙核、四核)
- 自動管理線程的生命週期(創建線程、調度任務、銷燬線程)
- 只需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼。
GCD的使用步驟
- 開發者定製將要執行的任務
- 將任務添加到隊列中,GCD會自動將隊列中的任務取出,放到對應的線程中執行。
GCD的隊列可以分爲2大類型:
- 串行隊列:隊列中的任務按順序執行(不會同時執行)
- 並行隊列:隊列中的任務會併發執行,任務執行完畢,不一定出隊列。只有前面的任務執行完了,纔會出隊列。
GCD的任務2大類型:
- 同步任務:優先級高,在線程中有執行順序,不會開啓新的線程。
- 異步任務:優先級低,在線程中執行沒有順序,看cpu閒不閒。在主隊列中不會開啓新的線程,其他隊列會開啓新的線程。
隊列:可理解爲是管理任務的,它裏面放着很多的任務,來管理這些任務的開啓順序,串行隊列開啓異步任務,是有順序的
任務同步異步:決定的是任務的執行順序,並行隊列裏開啓同步任務是有執行順序的,只有異步纔沒有順序
串行隊列創建:
- 使用 dispatch_queue_create 函數創建串行隊列:創建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)
- dispatch_queue_t queue = dispatch_queue_create("serial_queue", NULL);
- 使用主隊列(跟主線程相關聯的隊列)主隊列是GCD自帶的一種特殊的串行隊列:放在主隊列中的任務,都會放到主線程中執行。可以使用dispatch_get_main_queue()獲得系統提供的主隊列:
dispatch_queue_tqueue = dispatch_get_main_queue();
併發隊列的創建:
- 使用 dispatch_queue_create 函數創建併發隊列。
dispatch_queue_tqueue = dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);
- 使用 dispatch_get_global_queue 獲得全局併發隊列。GCD默認已經提供了全局的併發隊列,供整個應用使用,可以無需手動創建。
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
多線程的應用
- 子線程與主線程的通信:有時需要在子線程處理一個耗時比較長的任務,並且此任務完成後,要在主線程執行另一個任務。
例如,從網絡加載圖片(在子線程),加載完成就更新UIView(在主線程)。
首先拿到全局併發隊列(或自己開啓一個子線程)來執行耗時的操作,然後在其完成block中拿到全局串行隊列來執行UI刷新的任務。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//加載圖片
NSData*dataFromURL = [NSData dataWithContentsOfURL:imageURL];
UIImage*imageFromData = [UIImageimageWithData:dataFromURL];
dispatch_async(dispatch_get_main_queue(), ^{
//加載完成更新view
UIImageView*imageView = [[UIImageView alloc] initWithImage:imageFromData];
});
});
- dispatch_once:用於在程序啓動到終止,只執行一次的代碼。此代碼被執行後,相當於自身全部被加上了註釋,不會再執行了。
爲了實現這個需求,我們需要使用 dispatch_once 讓代碼在運行一次後即刻被“雪藏”。
//使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只執行1次的代碼,這裏默認是線程安全的:不會有其他線程可以訪問到這裏
});
- dispatch_group:執行多個耗時的異步任務,但是隻能等到這些任務都執行完畢後,才能在主線程執行某個任務。爲了實現這個需求,我們需要讓將這些異步執行的操作放在 dispatch_group_async 函數中執行,最後再調用 dispatch_group_notify 來執行最後執行的任務。
dispatch_group_tgroup = 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(), ^{
//等前面的異步操作都執行完畢後,回到主線程...
});
- dispatch_barrier:有時要執行幾個不同的異步任務,但是我們還是要將其分成兩組:當第一組異步任務都執行完成後才執行第二組的異步任務。
爲了實現這個需求,我們需要使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);在兩組任務之間形成“柵欄”,使其“下方”的異步任務在其“上方”的異步任務都完成之前是無法執行的。
dispatch_queue_tqueue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{
NSLog(@"----任務1-----");
});
dispatch_async(queue,^{
NSLog(@"----任務2-----");
});
dispatch_barrier_async(queue, ^{
NSLog(@"----任務柵欄-----");
});
dispatch_async(queue,^{
NSLog(@"----任務3-----");
});
dispatch_async(queue,^{
NSLog(@"----任務4-----");
});