多線程(四種實現方式)
1.NSThread
2.NSObject
3.NSOperation/NSOperationQueue
4.GCD
進程:一個獨立運行的程序可以看做是一個進程。
線程:進程中的獨立代碼段。
通常一個程序只有一個進程,一個進程有1-n個線程(最少有一個主線程運行程序)
只有主線程的程序叫單線程程序,有主線程和子線程的程序稱為到線程程序
<h6>應用程序會默認為主線程分配1M的棧空間,默認為子線程分為512K的棧空間(分配必須是4K的整數倍)。主線程和子線程的棧區是相互獨立的。</h6>
<h6>主線程從main函數開始,所有的內容全部在自動釋放池管轄范圍內。子線程新開辟的區域在處理任務時,并沒有在自動釋放池的管轄范圍內,若在堆區開辟空間而不進行內存處理,會造成大量的泄漏(比如短時間內循環使用便利構造器等),所以人為開辟子線程必須要添加@autoreleasepool來管轄堆區的內容。主線程和子線程的堆區是共有的。
<1>NSThread
1.1使用示例對象來開辟子線程(手動開啟)
// 參數:方法的執行者、子線程需要執行的方法、傳遞的參數
NSThread *thread =[[NSThread alloc]initWithTarget:self selector:@selector(calculateAction) object:nil];
[thread start];// 必須手動開動才會執行子線程內容
// 取消子線程
// [thread cancel];
// 查看當前線程是否正在執行
NSLog(@"在執行%d",[thread isExecuting]);
// 查看當前線程是否取消
NSLog(@"已取消%d",[thread isCancelled]);
// 打印當前方法是否為主線程
NSLog(@"是否為主線程%d",[NSThread isMainThread]);
// 打印當前線程
NSLog(@"當前線程%@",[NSThread currentThread]);
1.2使用靜態方法來開辟子線程(自動開啟)
[NSThread detachNewThreadSelector:@selector(calculateAction) toTarget:self withObject:nil];
<2>NSObject
[self performSelectorInBackground:@selector(calculateAction) withObject:nil];
NSLog(@"主線程中開辟子線程代碼.....");
<3>NSOperationQueue:
此種多線程方式和NSOperation的兩個子類來結合使用,實現多線程方式
// 3.1
NSInvocationOperation *invocation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(calculateAction) object:nil];
// 3.2
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
[self calculateAction];
}];
// 創建管理隊列的兩個任務
NSOperationQueue *queue = [NSOperationQueue new];
// 設置最大并發數(同時執行任務的個數)
// 若最大并發數設置為1,那么任務將按照串行方式來執行
[queue setMaxConcurrentOperationCount:1];
// 將任務添加到隊列
[queue addOperation:invocation];
[queue addOperation:block];
<4>GCD
// 兩種隊列方式:并行隊列,串行隊列
// 并行隊列:所有任務并發執行,不分先后
// 串行隊列:所有任務依次執行,排隊完成
// GCD中,有三種隊列可以使用
// 主隊列:系統提供的單列,屬于串隊列
// 全局隊列:系統提供的單列,屬于并行隊列
// 自定義隊列:開發人員可以自定義選擇使用串行或者并行
4.1主隊列(int a = 10;主隊列的任務在主線程中執行)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 給隊列添加任務
dispatch_async(mainQueue, ^{
NSLog(@"第一個任務%d",[NSThread isMainThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"第二個任務%d",[NSThread isMainThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"第三個任務%d",[NSThread isMainThread]);
});
4.2全局隊列:并行隊列,會開辟子線程,但是其管理的任務不一定只在子線程執行,也會添加到主線程執行
// 1.隊列優先級
// 2.空余參數,以后添加作用
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 必須先執行我(第一個加入隊列時,才會優先執行此任務(在主線程中執行),否則和其他三個任務一樣無法確認哪個先執行)
dispatch_barrier_sync(globalQueue, ^{// 此處是_sync不是_async
NSLog(@"優先執行的代碼......%d",[NSThread isMainThread]);
});
// 向隊列中添加任務(三個任務在子線程中同步執行,打印結果沒有固定的先后順序)
dispatch_async(globalQueue, ^{
NSLog(@"第一個任務%d",[NSThread isMainThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"第二個任務%d",[NSThread isMainThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"第三個任務%d",[NSThread isMainThread]);
});
延時啟動任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), globalQueue, ^{
NSLog(@"出來吧,神龍!");
});
反復執行n次的任務
dispatch_apply(3, globalQueue, ^(size_t time) {
NSLog(@"第%zu次",time);
});
函數
void function(void * string);
void function(void * string){
NSLog( @"%s",string);
}
添加此函數到隊列
// 1.要把任務添加到的隊列
// 2.函數的參數
// 3.函數
dispatch_async_f(globalQueue, "fall in love with", function);
4.3自定義隊列
串行隊列
// 1.給隊列一個標簽,后續可以獲取隊列的標簽來操作
dispatch_queue_t serialQueue = dispatch_queue_create("com.lanou3g.mySerialQueue", DISPATCH_QUEUE_SERIAL);
// async和sync添加任務的區別在于是否同時執行block和外部的代碼(即子線程和主線程是否同時執行),前者會同時執行,后者則是順序執行
dispatch_async(serialQueue, ^{
NSLog(@"第一個任務%d",[NSThread isMainThread]);
});
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"第二個任務%d",[NSThread isMainThread]);
});
NSLog(@"2");
dispatch_async(serialQueue, ^{
NSLog(@"第三個任務%d",[NSThread isMainThread]);
});
NSLog(@"3");
并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
/*
dispatch_async(concurrentQueue, ^{
NSLog(@"第一個任務%d",[NSThread isMainThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"第二個任務%d",[NSThread isMainThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"第三個任務%d",[NSThread isMainThread]);
});
*/
/*
// 分組:使用一個組來操作一組任務
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"第一個分組任務%d",[NSThread isMainThread]);
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"第二個分組任務%d",[NSThread isMainThread]);
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"第三個分組任務%d",[NSThread isMainThread]);
});
// 等待分組中所有任務執行完畢后再執行此任務(必須寫在后面)
dispatch_group_notify(group, concurrentQueue, ^{
NSLog(@"撩漢終結者,kingStar");
});
*/
子線程回到主線程的演示
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加子任務
dispatch_async(globalQueue, ^{
[self calculateAction];
});
// 主、全局無論哪種隊列都遵循FIFO(先進先出)
用來耗時的計算(多線程測試的使用)
- (void)calculateAction{
// 讓子線程睡10秒再干活
[NSThread sleepForTimeInterval:2];
// 當子線程中出現對象類型時,需要使用自動釋放池包裹對應的代碼
@autoreleasepool {
int sum = 0;
for (int i = 0; i < 655350000; i++ ){
sum += i;
}
NSLog(@"%d",sum);
// 子線程回到主線程的第一種方式
// 1.執行在主線程的方法
// 2.傳遞參數
// 3.是否等到完成后操作
NSNumber *nsnum = [NSNumber numberWithInt:sum ];
[self performSelectorOnMainThread:@selector(mainAction:) withObject:nsnum waitUntilDone:YES];
// 打印當前方法是否為主線程
NSLog(@"===%d",[NSThread isMainThread]);
// 打印當前線程
NSLog(@"===%@",[NSThread currentThread]);
// 子線程回到主線程的第二種方式
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%d",sum);
NSLog(@"%d",[NSThread isMainThread]);
});
}
}
// 子線程回到主線程的第一種方式調用的mainAction:方法
- (void)mainAction:(NSNumber *)sum{
NSLog(@"計算的結果為%d",[sum intValue]);
NSLog(@"當前線程是否為主線程%d",[NSThread isMainThread]);
}
線程按照生命周期分為兩種
脫離線程:線程使用完畢后,即被銷毀,不可在喚醒
非脫離線程:線程任務執行時,始終處于被喚醒的狀態,一旦收集到任務,馬上啟動,生命周期長,可被喚醒。
// 多線程操作(兩隊人購票)
// 初始化開辟空間
self.lock = [NSLock new];
// 多線程容易出現線程互斥問題
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 第一隊人在購票
for (int i = 0; i < 1000; i++) {
[self buyTicket:@"王沖"];
}
});
dispatch_async(globalQueue, ^{
// 第二隊人在購票
for (int i = 0; i < 1000; i++) {
[self buyTicket:@"JonKing"];
}
});
// atomic和nonatomic實現
// atomic是線程安全的,使用@synchronized進行對對象的加鎖操作,但是只能是對象類型。
// nonatomic是線程不安全的,沒有對數據做任何處理。
// 舉例代碼:下列代碼的意思:當訪問到self對象時,寫在大括號內部的代碼會被保證同一時間只允許一個任務在訪問。
@synchronized(self) {
// 操作
}
購票方法
int count = 5000;
- (void)buyTicket:(NSString *)str{
// 每次遭遇訪問時,要進行加鎖操作
[self.lock lock];
count -- ;// 模擬每次買票造成的結果,票數減一
NSLog(@"%@購的一張票,剩余%d張票",str,count);
// 資源訪問完畢時,進行解鎖讓下一個任務訪問
[self.lock unlock];
}