多線程是iOS開發中的一個重要的環節,不論是在平常的開發,仍是面試中,都有着舉足輕重的地位,因此打算開闢一個小專題,研究多線程相關的底層原理。這一篇章是第一篇,介紹一些基礎的概念性的多線程。前端
當一個程序進入內存運行時,即變成一個進程。進程是處於運行過程當中的程序,而且具備必定的獨立功能,進程是系統進行資源分配和調度的一個獨立單位。每一個進程之間是獨立的,每一個進程均運行在其專用的且受保護的內存。程序員
在iOS
開發中,一個App在內存中就是一個進程,且相互獨立,只能訪問本身的沙盒控件,這也是蘋果運行可以流暢安全的一個主要緣由。面試
線程的定義,主要能夠歸結爲如下3點:後端
地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。數組
資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu等,可是進程之間 的資源是獨立的。安全
一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程 都死掉。因此多進程要比多線程健壯。多線程
進程切換時,消耗的資源大,效率高。因此涉及到頻繁的切換時,使用線程要好於進程。 一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程不能用進程併發
執行過程:每一個獨立的進程有一個程序運行的入口、順序執行序列和程序入口。可是線 程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。異步
線程是處理器調度的基本單位,可是進程不是。socket
runloop
與線程是一一對應的,一個runloop
對應一個核心的線程,爲何說是核心的, 是由於runloop
是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏。runloop
是來管理線程的,當線程的runloop
被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務。runloop
在第一次獲取時被建立,在線程結束時被銷燬。runloop
在程序一啓動就默認建立好了。runloop
是懶加載的,只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意:確保子線程的runloop
被建立,否則定時器不會回調隊列,又稱爲佇列(queue
),是先進先出(FIFO, First-In-First-Out
)的線性表,在具體應用中一般用鏈表或者數組來實現。裝載線程任務的隊形結構。隊列只容許在後端(稱爲rear
)進行插入操做,在前端(稱爲front
)進行刪除操做。隊列的操做方式和堆棧相似,惟一的區別在於隊列只容許新數據在後端進行添加。
隊列的類型決定了任務的執行方式(併發、串行),隊列包括如下幾種:
Concurrent Dispatch Queue
): 線程執行能夠同時一塊兒進行執行,不須要上一個執行完,就能執行下一個的。Serial Dispatch Queue
): 線程執行只能依次逐一前後有序的執行,等待上一個執行完,再執行下一個。同步 sync
: 只能在當前線程按前後順序依次執行任務,不具有開啓新線程的能力。
異步 async
: 在新的線程中執行任務,具有開啓新線程的能力。
一個進程中能夠併發多個線程同時執行各自的任務,叫作多線程。
分時操做系統會把CPU
的時間劃分爲長短基本相同的時間區間,叫時間片,在一個時間片內,CPU
只能處理一個線程中的一個任務,對於一個單核CPU來講,在不一樣的時間片來執行不一樣線程中的任務,就造成了多個任務在同時執行的「假象」。
多線程即爲單位時間片裏快速在各個線程之間切換
start
以後就會runnable
,而後等待cpu
分配資源,進行調用cpu
調度以後就會到running
狀態sleep
,或者死鎖等操做以後就會形成線程阻塞.當阻塞解除以後不會直接到running
狀態而是又到就緒狀態優勢:
缺點:
pthread
:即POSIX Thread
,縮寫稱爲pthread
,是線程的POSIX
標準,是一套通用的多線程API
,能夠在Unix/Linux/Windows
等平臺跨平臺使用。iOS中基本不使用。NSThread
:蘋果封裝的面向對象的線程類,能夠直接操做線程,比起GCD
,NSThread
效率更高,由程序員自行建立,當線程中的任務執行完畢後,線程會自動退出,程序員也可手動管理線程的生命週期。使用頻率較低。GCD
:全稱Grand Central Dispatch
,由C語言實現,是蘋果爲多核的並行運算提出的解決方案,CGD
會自動利用更多的CPU
內核,自動管理線程的生命週期,程序員只須要告訴GCD
須要執行的任務,無需編寫任何管理線程的代碼。GCD
也是iOS使用頻率最高的多線程技術。NSOperation
:基於GCD
封裝的面向對象的多線程技術,常配合NSOperationQueue
使用,使用頻率較高。
GCD
僅僅支持FIFO
隊列,不支持異步操做之間的依賴關係設置。而NSOperation
中的隊列能夠被從新設置優先級,從而實現不一樣操做的執行順序調整。NSOperation
支持KVO
,能夠觀察任務的執行狀態。GCD
更接近底層,GCD
在追求性能的底層操做來講,是速度最快的。GCD
須要本身寫更多的代碼來實現,而NSOperation
已經內建了這些支持NSOperation
更好。底層代碼中,任務之間不太互相依賴,而須要更高的併發能力,GCD
則更有優點線程池是多線程處理的一種形式,處理過程當中將任務添加到隊列,而後在建立線程後自動啓動這些任務。線程池中的線程都是後臺線程。每一個線程都有默認的堆棧大小,以默認的優先級運行,並處在多線程單元中。
AbortPolicy
:默認策略。直接拋出RejectedExecutionExeception
異常阻止系統正常運行,該異常由調用者捕獲CallerRunsPolicy
:調節機制。既不拋棄也不報異常。將任務回退給調用者DisOldestPolicy
:丟掉等待最久的任務DisCardPolicy
:直接丟棄任務蘋果的官方文檔中,給出了幾種線程間通訊的方式:
performSelector
的一系列方法,能夠實現由某一線程指定在另外的線程上執行任務。由於任務的執行上下文是目標線程,這種方式發送的消息將會自動的被序列化。Runloop sources
: 一個自定義的 Runloop source
配置可讓一個線程上收到特定的應用程序消息。因爲 Runloop source
是事件驅動的,所以在無事可作時,線程會自動進入睡眠狀態,從而提升了線程的效率。Ports and sockets
: 基於端口的通訊是在兩個線程之間進行通訊的一種更爲複雜的方法,但它也是一種很是可靠的技術。更重要的是,端口和套接字可用於與外部實體(例如其餘進程和服務)進行通訊。爲了提升效率,使用 Runloop source
來實現端口,所以當端口上沒有數據等待時,線程將進入睡眠狀態。Cocoa
分佈式對象: 分佈式對象是一種 Cocoa 技術,可提供基於端口的通訊的高級實現。儘管能夠將這種技術用於線程間通訊,可是強烈建議不要這樣作,由於它會產生大量開銷。分佈式對象更適合與其餘進程進行通訊,儘管在這些進程之間進行事務的開銷也很高對於performSelector
的通訊,平時在開發中運用的比較多,就不詳細講述了,咱們講一個線程以前,運用端口來傳遞消息的例子來加深印象。
首先咱們建立一個類來發送消息:
@interface WYPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end
複製代碼
#import "WYPerson.h"
@interface WYPerson()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *vcPort;
@property (nonatomic, strong) NSPort *myPort;
@end
@implementation WYPerson
- (void)personLaunchThreadWithPort:(NSPort *)port{
NSLog(@"VC 響應了Person裏面");
@autoreleasepool {
✅//1. 保存主線程傳入的port
self.vcPort = port;
✅//2. 設置子線程名字
[[NSThread currentThread] setName:@"WYPersonThread"];
❗️//3. 開啓runloop(重點在這)
[[NSRunLoop currentRunLoop] run];
✅//4. 建立本身port
self.myPort = [NSMachPort port];
✅//5. 設置port的代理回調對象(NSMachPortDelegate)
self.myPort.delegate = self;
✅//6. 完成向主線程port發送消息
[self sendPortMessage];
}
}
/** * 完成向主線程發送port消息 */
- (void)sendPortMessage {
NSData *data1 = [@"WY" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
✅// 發送消息到VC的主線程
✅// 第一個參數:發送時間。
✅// msgid 消息標識。
✅// components,發送消息附帶參數。
✅// reserved:爲頭部預留的字節數
[self.vcPort sendBeforeDate:[NSDate date]
msgid:10086
components:array
from:self.myPort
reserved:0];
}
#pragma mark - NSMachPortDelegate 處理端口傳遞信息
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"person:handlePortMessage == %@",[NSThread currentThread]);
NSLog(@"從VC 傳過來一些信息:");
NSLog(@"components == %@",[message valueForKey:@"components"]);
NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}
複製代碼
接着咱們建立PortViewController
用來接收消息和回調消息給發送者
#import "PortViewController.h"
#import <objc/runtime.h>
#import "WYPerson.h"
@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) WYPerson *person;
@end
@implementation PortViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Port線程通信";
self.view.backgroundColor = [UIColor whiteColor];
✅//1. 建立主線程的port
✅// 子線程經過此端口發送消息給主線程
self.myPort = [NSMachPort port];
✅//2. 設置port的代理回調對象
self.myPort.delegate = self;
❗️//3. 把port加入runloop,接收port消息(此時主線程已經再跑,不須要run)
[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
self.person = [[WYPerson alloc] init];
[NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
toTarget:self.person
withObject:self.myPort];
}
#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"VC == %@",[NSThread currentThread]);
NSLog(@"從person 傳過來一些信息:");
//會報錯,沒有這個隱藏屬性
//NSLog(@"from == %@",[message valueForKey:@"from"]);
NSArray *messageArr = [message valueForKey:@"components"];
NSString *dataStr = [[NSString alloc] initWithData:messageArr.firstObject encoding:NSUTF8StringEncoding];
NSLog(@"傳過來一些信息 :%@",dataStr);
NSPort *destinPort = [message valueForKey:@"remotePort"];
if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
NSLog(@"傳過來的數據有誤");
return;
}
NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
❗️❗️❗️// 很是重要,若是你想在Person的port接受信息,必須加入到當前主線程的runloop
[[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
NSLog(@"VC == %@",[NSThread currentThread]);
BOOL success = [destinPort sendBeforeDate:[NSDate date]
msgid:10010
components:array
from:self.myPort
reserved:0];
NSLog(@"%d",success);
}
- (void)getAllProperties:(id)somebody{
u_int count = 0;
objc_property_t *properties = class_copyPropertyList([somebody class], &count);
for (int i = 0; i < count; i++) {
const char *propertyName = property_getName(properties[i]);
NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
}
}
複製代碼
打印結果以下:
經過上面的例子,咱們已經實現了線程之間的通訊
稍微總結一下NSPort
的使用要點:
NSPort
對象必須添加到要接收消息的線程的Runloop
中,必須由Runloop
來進行管理NSPortDelegate
協議的-handlePortMessage:
方法來獲取消息內容