iOS 再學(xué)多線程(一)

噢,達(dá)令

之前“寫(xiě)”過(guò)一篇關(guān)于多線程的博客,主要是綜合各路大神的博文寫(xiě)得,回過(guò)頭看其實(shí)對(duì)于多線程的相關(guān)知識(shí)點(diǎn)還是模糊的,從今天起重新認(rèn)識(shí)多線程。

基礎(chǔ)知識(shí)

進(jìn)程、線程

進(jìn)程
進(jìn)程

進(jìn)程,指的是系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,每個(gè)進(jìn)程是獨(dú)立的,簡(jiǎn)言之: 正在進(jìn)行的程序即進(jìn)程。
線程和線程是密不可分的,一個(gè)進(jìn)程想要運(yùn)行并執(zhí)行任務(wù),必須有線程才行。一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行的。
例如: 使用迅雷下片兒都需要線程,一般我們?cè)诠こ讨?,將耗時(shí)的任務(wù)放到子線程中執(zhí)行。

線程
線程

串行

在同一時(shí)間內(nèi),一個(gè)線程只能執(zhí)行一個(gè)任務(wù),在串行中,如果要在一個(gè)線程中執(zhí)行多個(gè)任務(wù)(串行),那么只能一個(gè)一個(gè)的按順序執(zhí)行這些任務(wù)。

例如排隊(duì)辦業(yè)務(wù),別插隊(duì),挨打!
例如排隊(duì)辦業(yè)務(wù),別插隊(duì),挨打!

多線程

一個(gè)進(jìn)程中可以開(kāi)啟多條線程,每條線程可以并行(同時(shí))進(jìn)行,用于執(zhí)行不同的任務(wù)。

下片兒
下片兒

如圖:

并行
并行

多線程原理

同一時(shí)間,CPU 只能處理一條線程的任務(wù),只有一條線程工作。多線程并發(fā)同時(shí)執(zhí)行,其實(shí)是 CPU 快速地在多條線程之間調(diào)度,來(lái)回切換調(diào)度,造成了“同時(shí)”執(zhí)行的假象,只不過(guò) CPU 切換時(shí)間很快。其實(shí)也可以理解為“串行”。

多線程的缺點(diǎn)

  1. 多線程如果在很多條線程之間切換調(diào)度, CPU 會(huì)消耗大量的 CPU 資源,CPU 的開(kāi)銷(xiāo)會(huì)很大,很累;
  2. 多線程開(kāi)的過(guò)多會(huì)降低每條線程的調(diào)動(dòng)頻次,造成線程執(zhí)行效率低;
  3. 創(chuàng)建線程對(duì)內(nèi)存是有開(kāi)銷(xiāo)的,而且需要時(shí)間,大約 90 毫秒的創(chuàng)建時(shí)間,所以創(chuàng)建的線程越多占用空間還有耗費(fèi)時(shí)間也會(huì)越多、越久;
  4. 線程創(chuàng)建的越多,對(duì)于項(xiàng)目的設(shè)計(jì)會(huì)更加復(fù)雜,比如線程之間的通訊,多線程的數(shù)據(jù)共享。

常用多線程方案對(duì)比

以下為我們項(xiàng)目中的常用多線程方案,其中 GCD 多線程方案,使用相對(duì)多,相對(duì)“牛逼“,恩,是牛逼,面試沒(méi)回答好 /(ㄒoㄒ)/ 。其中的 pthread 使用時(shí)需要導(dǎo)入 pthread 頭文件,使用 C 語(yǔ)言實(shí)現(xiàn)。

多線程方案對(duì)比
多線程方案對(duì)比

NSThread 線程創(chuàng)建

我們?cè)?viewController 中創(chuàng)建子線程,創(chuàng)建方法常用的有 ”3種“。

#pragma mark - 在touche方法中調(diào)用創(chuàng)建線程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [self creatThread1];
    [self creatThread2];
    [self creatThread3];

}


#pragma mark -- 隱式創(chuàng)建子線程 (不能更詳細(xì)的設(shè)置線程相關(guān)的信息)
- (void)creatThread3{
    
    [self performSelector:@selector(run:) withObject:@"jake" ];
}

#pragma mark -- 自動(dòng)創(chuàng)建線程 自動(dòng)開(kāi)啟
- (void)creatThread2{

    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"Mob"];
}

#pragma mark - 手動(dòng)創(chuàng)建 線程創(chuàng)建完,會(huì)自動(dòng)銷(xiāo)毀,不用程序員管理

- (void)creatThread1{

    //創(chuàng)建線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"run"];
    //線程命名
    thread.name = @"線程1";
    //開(kāi)啟線程
    [thread start];
}


- (void)run: (NSString *)pat{

    NSLog(@"pat : %@ --- %@ ", pat , [NSThread currentThread].name);
    
    [NSThread sleepForTimeInterval:2];//阻塞線程,讓線程休眠兩秒后執(zhí)行
    
    //遙遠(yuǎn)的未來(lái) 暫停到某個(gè)時(shí)刻
    //[NSThread sleepUntilDate:[NSDate distantFuture]];
        NSLog(@"阻塞線程,讓線程休眠兩秒后執(zhí)行");
}

多線程安全

多條線程訪問(wèn)同一個(gè)地址,或是同時(shí)做同一件事情時(shí),會(huì)出現(xiàn)訪問(wèn)錯(cuò)誤的情況,例如買(mǎi)票、取款。

解決方法: 需要使用線程鎖(互斥鎖),在取值前添加,一般我們將 self 作為鎖對(duì)象,鎖對(duì)象一定要注意,只有一個(gè),多個(gè)鎖對(duì)象還會(huì)出現(xiàn)訪問(wèn)問(wèn)題。

線程同步和線程鎖為同一件事。

@synchronized (self) {
    
}

GCD 多線程

全稱是Grand Central Dispath (牛逼的中樞調(diào)度器),純C語(yǔ)言,提供非常多強(qiáng)大的函數(shù),是目前蘋(píng)果官網(wǎng)推薦的多線程開(kāi)發(fā)方法,NSOperation便是基于GCD的封裝。

GCD中有2個(gè)核心概念

(1)任務(wù):執(zhí)行什么操作
(2)隊(duì)列:用來(lái)存放任務(wù)

容易混淆:同步、異步、并發(fā)、串行

同步和異步主要影響:能不能開(kāi)啟新的線程

同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力

并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式

并發(fā):允許多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)

并行和串行不會(huì)影響是否能開(kāi)啟線程。

GCD 自己可以創(chuàng)建 串行隊(duì)列, 也可以創(chuàng)建并行隊(duì)列.它有兩個(gè)參數(shù),第一個(gè)參數(shù)是標(biāo)識(shí)符,用于 DEBUG 的時(shí)候標(biāo)識(shí)唯一的隊(duì)列,可以為空。第二個(gè)才是最重要的。第二個(gè)參數(shù)用來(lái)表示創(chuàng)建的隊(duì)列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊(duì)列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊(duì)列。

//串行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("GCD多線程,串行", DISPATCH_QUEUE_SERIAL);

// 異步任務(wù)
dispatch_async(queue1, ^{
   NSLog(@"異步執(zhí)行任務(wù),開(kāi)線程,在串行隊(duì)列中執(zhí)行");
   });
}];


 //并行隊(duì)列
dispatch_queue_t queue2 = dispatch_queue_create("GCD多線程,并行", DISPATCH_QUEUE_CONCURRENT);
也可以:
dispatch_queue_t queue2 = dispatch_queue_create("GCD多線程,并行", NULL);

//同步任務(wù)
dispatch_sync(queue2, ^{
NSLog(@"同步任務(wù):不會(huì)開(kāi)啟線程,在并行隊(duì)列中執(zhí)行");

});

GCD 官方已經(jīng)提供了一個(gè)全局的并發(fā)隊(duì)列,供整個(gè)應(yīng)用使用

dispatch_queue_t queque3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_barrier_async 任務(wù) “柵欄”

前面任務(wù)執(zhí)行完之后才會(huì)執(zhí)行它,執(zhí)行完之后才會(huì)執(zhí)行后面的。

    // 全局并發(fā)隊(duì)列  在 barrier 任務(wù)中不能使用全局隊(duì)列 ×
dispatch_queue_t quent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
// 要使用這個(gè)隊(duì)列 √
dispatch_queue_t quent2 = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);

dispatch_async(quent2, ^{
    
    NSLog(@"---1--- %@",[NSThread currentThread]);
});

// “柵欄” 前面的任務(wù)執(zhí)行完后,再執(zhí)行 barrier 里面的,執(zhí)行完之后才會(huì)執(zhí)行后面的任務(wù)
dispatch_barrier_async(quent2, ^{
    
    NSLog(@"---柵欄 2--- %@",[NSThread currentThread]);
    
    [self run:@"li"];
    
});

dispatch_async(quent2, ^{
    NSLog(@"---3--- %@",[NSThread currentThread]);
});

主隊(duì)列 特殊的隊(duì)列

在主隊(duì)列中的任務(wù)都會(huì)在主線程中進(jìn)行。

dispatch_queue_t queue4 = dispatch_get_main_queue();
同步、異步 并發(fā)和串行隊(duì)列組合區(qū)別
同步、異步 并發(fā)和串行隊(duì)列組合區(qū)別

由于隊(duì)列和任務(wù)的不同,可以搭配出不同的組合,如上圖,大體上的問(wèn)題有兩個(gè),一個(gè)是否會(huì)創(chuàng)建新的子線程,還有是否會(huì)阻塞線程。

異步函數(shù)+并發(fā)隊(duì)列

可以同時(shí)開(kāi)啟多條線程

同步函數(shù) + 并發(fā)隊(duì)列

不可以開(kāi)新的線程,不能并發(fā)執(zhí)行任務(wù)(只要不能開(kāi)辟新的線程就不會(huì)并發(fā)執(zhí)行任務(wù))

異步函數(shù) + 串行隊(duì)列

可以開(kāi)辟新的子線程,但是不能并發(fā)執(zhí)行任務(wù),只能開(kāi)一條線程執(zhí)行任務(wù)

異步函數(shù) + 主隊(duì)列

不管你是同步還是異步操作,只要是主隊(duì)列,那么任務(wù)執(zhí)行都會(huì)在主線程中執(zhí)行,異步函數(shù)在主隊(duì)列中將不會(huì)開(kāi)辟線程。

同步函數(shù) + 串行隊(duì)列 同步函數(shù) + 主隊(duì)列

這兩個(gè)組合比較特殊,一般不會(huì)在項(xiàng)目中這樣干活搭配,這樣的組合會(huì)造成堵塞線程?!蹦阆取埃?”還是你先“ 讓來(lái)讓去,誰(shuí)都不能走了。

描述
描述

因?yàn)橹麝?duì)列要在主線程中執(zhí)行,我們假設(shè)函數(shù)就在當(dāng)前的主線程中,當(dāng)執(zhí)行主隊(duì)列時(shí),他想回到主線程,但是執(zhí)行主線程的時(shí)候,需要同步的任務(wù)執(zhí)行完才能執(zhí)行主線程。然而此刻同步任務(wù)也想執(zhí)行完,但是主線程還沒(méi)執(zhí)行完。把自己繞蒙逼了 /(ㄒoㄒ)/~~

簡(jiǎn)而言之: 主隊(duì)列要執(zhí)行,就得回到主線程,但是,同步任務(wù)還沒(méi)執(zhí)行完畢,無(wú)法完成祖國(guó)回歸,回到主線程懷抱的夢(mèng)想,所以就很尷尬了,最終隔岸相望。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容