線程管理

術(shù)語

任務(wù):準(zhǔn)備執(zhí)行的操作,通常就是一個(gè)block塊

隊(duì)列:queue,存放任務(wù),管理任務(wù)

進(jìn)程——蘋果電腦里的活動(dòng)監(jiān)視器App里詳細(xì)羅列了操作系統(tǒng)此時(shí)此刻正在運(yùn)行的所有程序,同時(shí)標(biāo)明了每一個(gè)進(jìn)行的程序的所有線程。

線程——將一個(gè)程序都轉(zhuǎn)換成匯編的CPU命令時(shí),由一堆不分叉的Cpu指令組成的路徑(主線程命令和分線程命令)

多線程編程——由多條不分叉的CPU指令所組成的路徑就是多線程編程。優(yōu)點(diǎn):提高資源利用率和程序執(zhí)行效率,用戶體驗(yàn) 缺點(diǎn):1、上下文切換頻率太高會(huì)影響性能 2、資源競爭—>上鎖(類似單例)3、死鎖 4、開辟的線程其實(shí)類似于創(chuàng)建一個(gè)對(duì)象,會(huì)消耗大量內(nèi)存(內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時(shí)間)5、程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

CPU包括——1、物理CPU(硬件)2、CPU核 (一個(gè)物理CPU可以虛擬出多個(gè)CPU核,一個(gè)CPU核就相當(dāng)于一個(gè)CPU的功能,可以分身,一個(gè)CPU核一個(gè)時(shí)刻只能執(zhí)行一個(gè)CPU命令)

并發(fā)——雖然同一時(shí)間,CPU只能處理1條線程,只有1條線程在執(zhí)行,但是CPU可以快速地在多條線程之間調(diào)度切換,如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。當(dāng)然如果線程過多,會(huì)消耗大量的CPU資源,同時(shí)每條線程被調(diào)度執(zhí)行的頻次會(huì)降低(線程的執(zhí)行效率降低)

多核就快

首先談一談就是程序執(zhí)行的本質(zhì),我們寫的代碼都首先是轉(zhuǎn)化成一行行的匯編代碼才能被計(jì)算機(jī)所識(shí)別。這一行行的匯編代碼其實(shí)也叫做CPU指令。這里面就涉及到CPU了,CPU就是讀取這些指令的工具。當(dāng)然,有一種匯編代碼也就是CPU指令的結(jié)構(gòu)是從頭到尾不間斷的,也就是這些CPU指令一點(diǎn)也沒有分叉,從頭到尾都是直路,就像高速公路一樣,只有直路,沒有岔路??墒浅绦虻镊攘驮谟冢龅椒植砺窌r(shí)或更復(fù)雜的十字路口時(shí)應(yīng)該怎么辦?肯定不能一根筋呀!于是CPU廠商就想到了一個(gè)方法,就是分身術(shù),當(dāng)遇到分叉路的時(shí)候就像孫悟空一樣,復(fù)制一個(gè)自己來走新出現(xiàn)的分岔路??墒菃栴}來了,如果第二次又遇到分叉路怎么辦呢?兩種思路,第一種再復(fù)制一個(gè)自己。第二種就是用那個(gè)復(fù)制的自己輪流前進(jìn)。第一種方法主要受到CPU本身性能的局限。有的CPU只能復(fù)制一個(gè)自己,算上本身總共才兩個(gè)。也有的CPU可以復(fù)制三個(gè)自己,算上本身總共有4個(gè)。這種具體去跑馬路的車就叫做CPU核,所以你會(huì)發(fā)現(xiàn),CPU越強(qiáng)大,也就是能夠復(fù)制的自己越多,能夠同時(shí)走的分叉路就越多,自然而然,四核就是比雙核快。第二種方法就是讓CPU的寄存器不斷受到考驗(yàn),當(dāng)CPU分配那個(gè)復(fù)制的自己在那一條道路上奔跑時(shí),必須時(shí)時(shí)記錄下來這個(gè)復(fù)制的自己已經(jīng)在每一條分叉路上已經(jīng)跑了多遠(yuǎn),并需要準(zhǔn)確無誤的保存在寄存器中,然后當(dāng)CPU再一次將那個(gè)復(fù)制的自己放在相應(yīng)地分岔路上繼續(xù)跑時(shí),就會(huì)讀取寄存器里面的數(shù)據(jù)來繼續(xù)前進(jìn)。

主線程刷UI

因?yàn)榉志€程不能刷新UI,但是有的時(shí)候明明看見刷新UI的代碼寫在了分線程里,但這并不代表這些代碼會(huì)被分線程(復(fù)制的CPU核)執(zhí)行,分線程主要是負(fù)責(zé)數(shù)據(jù)的下載。(兩種情況:1、下載完成后主動(dòng)回到主線程,告訴主線程自己已經(jīng)完成下載,可以讓主線程執(zhí)行分線程沒有資格執(zhí)行的代碼 2、需要主線程不定期去主動(dòng)檢查也就是說,盡管你把刷新UI的代碼寫在了分線程,但分線程只會(huì)執(zhí)行其中的下載代碼,根本不會(huì)執(zhí)行刷新UI的代碼。刷新UI的代碼會(huì)等到主線程不定期的主動(dòng)檢查分線程時(shí)才會(huì)被執(zhí)行。所以這樣會(huì)出現(xiàn)一些延時(shí),所以應(yīng)該在下載完成后主動(dòng)回到主線程)

時(shí)間片

如果同一個(gè)CPU核去管理多個(gè)線程時(shí),就會(huì)涉及到時(shí)間分配的問題,如果管理的是兩個(gè)線程,線程一有10個(gè)命令,線程二有20個(gè)命令,CPU核會(huì)將時(shí)間片分配給兩個(gè)線程,一一去執(zhí)行完兩個(gè)線程里面的所有命令,當(dāng)一個(gè)線程切換到另一個(gè)線程時(shí),系統(tǒng)會(huì)將當(dāng)前線程的信息保存到寄存器中,等下次切換過來到時(shí)候喚醒寄存器,讀取數(shù)據(jù),從上一次的命令接著處理。這就是上下文切換。上下文切換——時(shí)間片分配(隨機(jī)分配CPU核給那一個(gè)分線程,同時(shí)保存每一分線程上一次執(zhí)行到的位置到寄存器中,告訴CPU核現(xiàn)在該從第幾個(gè)CPU指令開始執(zhí)行)

iOS多線程

NSThread

  • 創(chuàng)建、啟動(dòng)線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
  • 線程相關(guān)用法
// 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];

// 線程名字
- (void)setName:(NSString *)name;
- (NSString *)name;

// 獲得主線程
+ (NSThread *)mainThread; 

// 判斷是否主線程
- (BOOL)isMainThread; 
+ (BOOL)isMainThread;

// 創(chuàng)建線程后自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

// 隱式創(chuàng)建并啟動(dòng)線程
[self performSelectorInBackground:@selector(run)  withObject:nil];

// 阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 強(qiáng)制停止線程
+ (void)exit;

GCD全稱Grand Central Dispatch

純C語言函數(shù),為多核并行運(yùn)算而生,自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核),且自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),只需告訴GCD想要執(zhí)行的任務(wù),根本無需寫任何管理線程生命周期的代碼。隊(duì)列管理任務(wù)執(zhí)行順序,同步異步?jīng)Q定任務(wù)執(zhí)行線程。

  • GCD創(chuàng)建并發(fā)隊(duì)列
// 創(chuàng)建并發(fā)隊(duì)列(隊(duì)列名稱,隊(duì)列類型)
dispatch_queue_t   myQueue = dispatch_queue_create(const char *label,   dispatch_queue_attr_t attr);

// 獲得默認(rèn)并發(fā)隊(duì)列(隊(duì)列優(yōu)先級(jí),缺省參數(shù)0)
dispatch_queue_t  systemQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

// 任務(wù)加入并發(fā)隊(duì)列
dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
});
  • GCD創(chuàng)建串行隊(duì)列
// 創(chuàng)建串行隊(duì)列(隊(duì)列名稱,隊(duì)列類型)
dispatch_queue_t  myQueue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);

// 直接獲取主隊(duì)列 = 特殊串行隊(duì)列
dispatch_queue_t  systemQueue = dispatch_get_main_queue();

// 任務(wù)加入串行隊(duì)列
dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
});
  • GCD同步執(zhí)行任務(wù)
// 繼續(xù)當(dāng)前線程(隊(duì)列,任務(wù))
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • GCD異步執(zhí)行任務(wù)
// 開啟新線程(隊(duì)列,任務(wù))
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • GCD線程通信
// 從子線程回到主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // 執(zhí)行耗時(shí)的異步操作...


        dispatch_async(dispatch_get_main_queue(), ^{
               // 回到主線程,執(zhí)行UI刷新操作

         });
});

// 對(duì)比performSelector的線程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
  • GCD延時(shí)執(zhí)行
// GCD延時(shí)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
});

// 對(duì)比performSelector延時(shí)
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

// 對(duì)比NSTimer延時(shí)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
  • GCD單例執(zhí)行
// 保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});


  • GCD創(chuàng)建定時(shí)器執(zhí)行代碼塊
// 創(chuàng)建Timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 設(shè)置定時(shí)器的觸發(fā)時(shí)間(1秒后)和時(shí)間間隔(每隔2秒)
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0);
// 設(shè)置Block回調(diào)
dispatch_source_set_event_handler(self.timer, ^{
     NSLog(@"Timer %@", [NSThread currentThread]);
});
// 開啟定時(shí)器
dispatch_resume(self.timer);


// 取消置空定時(shí)器
dispatch_cancel(self.timer);
self.timer = nil;
  • GCD實(shí)現(xiàn)任務(wù)依賴
// 隊(duì)列組管理任務(wù)一和任務(wù)二
dispatch_group_t group =  dispatch_group_create();

// 異步執(zhí)行任務(wù)一
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
});

// 異步執(zhí)行任務(wù)二
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
});

// 任務(wù)一和任務(wù)二都結(jié)束Block回調(diào)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
});
  • GCD分線程遍歷
/**
 *   GCD快速異步遍歷
 */
- (void)apply
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSString *from = @"初始路徑";
    NSString *to = @目標(biāo)路徑";
    
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];
    
    dispatch_apply(subpaths.count, queue, ^(size_t index) {
        NSString *subpath = subpaths[index];
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
        [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
        NSLog(@"%@---%@", [NSThread currentThread], subpath);
    });
}

/**
 *  對(duì)比傳統(tǒng)異步遍歷
 */
- (void)moveFile {
    NSString *from = @"初始路徑";
    NSString *to = @目標(biāo)路徑";

    NSFileManager *mgr = [NSFileManager defaultManager];
    NSArray *subpaths = [mgr subpathsAtPath:from];

    for (NSString *subpath in subpaths) {
        NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
        NSString *toFullpath = [to stringByAppendingPathComponent:subpath];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
        });
    }
}

NSOperation&&NSOperationQueue

NSOperation是個(gè)并不具備封裝操作能力的抽象類,必須使用它的子類來封裝操作,NSOperation子類包括NSInvocationOperation、NSBlockOperation、自定義子類繼承NSOperation。如果僅僅是實(shí)例化了一個(gè)操作對(duì)象,然后簡簡單單的執(zhí)行這個(gè)操作的start方法,這樣沒有絲毫意義,還不如在.m文件里面寫一個(gè)私有方法來得直接,可是一旦把實(shí)例化的NSOperation對(duì)象放到NSOperationQueue隊(duì)列之中,添加到隊(duì)列里面的操作對(duì)象會(huì)自動(dòng)開始調(diào)用操作對(duì)象的start方法,開始異步在一條新的線程里面執(zhí)行封裝在操作對(duì)象里面的一系列代碼,這就實(shí)現(xiàn)了分線程操作了,好神奇有木有!

  • 創(chuàng)建NSInvocationOperation操作
//  創(chuàng)建NSInvocationOperation對(duì)象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

//  默認(rèn)同步,除非添加操作到隊(duì)列
- (void)addOperation:(NSOperation *)op;
  • 創(chuàng)建NSBlockOperation操作
//  創(chuàng)建NSBlockOperation
+ (id)blockOperationWithBlock:(void (^)(void))block;

//  添加更多操作(只要任務(wù)數(shù)>1,就會(huì)異步執(zhí)行操作)
- (void)addExecutionBlock:(void (^)(void))block;


  • 創(chuàng)建自定義NSOperation操作
重寫父類NSOperation的main方法,在main方法里面寫入我想要封裝的執(zhí)行的任務(wù),但是有一點(diǎn)需要特別注意,就是自己寫的這段代碼很可能會(huì)是在子線程里面異步執(zhí)行,既然是異步操作,自然無法訪問主線程的自動(dòng)釋放池,對(duì)象的內(nèi)存釋放便是需要重點(diǎn)考慮的內(nèi)容。經(jīng)常的做法就是,在封裝想要執(zhí)行的代碼之前,必須先判斷一下這個(gè)操作對(duì)象是否已經(jīng)被取消,因?yàn)橐坏╅_始執(zhí)行進(jìn)入了子線程就無法在判斷isCancelled這個(gè)屬性了,所以必須在開始執(zhí)行封裝的代碼之前,必須先判斷這個(gè)操作對(duì)象是否已經(jīng)被取消。如果判斷已經(jīng)取消,我需要做的就是一些內(nèi)存的管理。
  • 創(chuàng)建NSOperation操作依賴
// 一定先讓操作A執(zhí)行完畢,然后才能執(zhí)行操作B
[operationB addDependency:operationA]; 
?。?!一定注意不能相互依賴
  • 監(jiān)聽NSOperation操作執(zhí)行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
  • 添加操作到NSOperationQueue對(duì)列
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
  • 設(shè)置操作對(duì)列NSOperationQueue最大并發(fā)數(shù)
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 對(duì)列NSOperationQueue的取消、暫停、恢復(fù)
//  取消隊(duì)列操作
- (void)cancelAllOperations;
- (void)cancel;

// 暫停Or恢復(fù)隊(duì)列
- (void)setSuspended:(BOOL)bool; 
- (BOOL)isSuspended;

GCD和NSOperation的區(qū)別?

GCD無法控制線程的最大并發(fā)數(shù),而NSOperation可以控制同時(shí)進(jìn)行的線程個(gè)數(shù)!

開辟多線程?

  • performSelector

作為NSObject的類別,因此只要是控制器的對(duì)象就可以直接調(diào)用這個(gè)方法。而且不僅僅可以通過performSelectorInBackground將耗時(shí)操作放進(jìn)分線程里,更可以通過performSelectorOnMainThread將分線程里的參數(shù)傳遞到主線程以便進(jìn)一步操作,例如刷新UI。

  • NSThread線程類

thread本身就是線的意思。通過NSThread類實(shí)例化一個(gè)對(duì)象。然后直接通過初始化方法就可以像為UIButton增添點(diǎn)擊事件那樣增添一個(gè)在分線程里進(jìn)行的方法。而且可以傳遞給這個(gè)分線程方法一個(gè)無論什么對(duì)象類型的參數(shù)。但是特別注意:通過NSThread類實(shí)例化的對(duì)象所初始化增添的分線程方法必須通過[對(duì)象 start]方法表示開始執(zhí)行分線程方法。而且無論第三方工具如何豐富,本質(zhì)上都是對(duì)NSThread進(jìn)行一系列操作。所以說在任何情況下都可以調(diào)用[NSThread currentThread]這個(gè)類方法獲取到當(dāng)前的線程信息,有點(diǎn)像代碼版的活動(dòng)監(jiān)視器哈!這是蘋果提供的三種方法里面相對(duì)輕量級(jí)的,但需要管理線程的生命周期、同步、加鎖的問題,這會(huì)導(dǎo)致一定得性能開銷。

  • GCD隊(duì)列Grand Central Dispatch

偉大的中樞調(diào)度器,充分發(fā)揮CPU內(nèi)核的性能,自動(dòng)管理線程的生命周期,包括創(chuàng)建線程,調(diào)度任務(wù)和銷毀線程。而且使用過程中只需添加任務(wù)即可,無需管理線程。

  • NSOperation

一塊資源可能會(huì)被多個(gè)線程共享,通常添加互斥鎖@synchronized(self){}來避免引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題。但是添加互斥鎖會(huì)影響手機(jī)的性能,所以盡量將加鎖和資源搶奪業(yè)務(wù)的邏輯交給服務(wù)器端處理,線程不會(huì)孤立存在,子線程下載圖片,在主線程刷新UI顯示圖片。使用@synchronized(self的鎖對(duì)象){}包起來的代碼同一時(shí)刻只能被一個(gè)線程執(zhí)行,而且加鎖的時(shí)候盡量縮小范圍,范圍越大就越消耗性能。

串行隊(duì)列Vs并行隊(duì)列?

相同點(diǎn)

  • 創(chuàng)建方式都是dispatch_queue_t queue,只不過創(chuàng)建隊(duì)列時(shí)的標(biāo)志符后面的參數(shù)有了一些變化,主要有(NULL, DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_CONCURRENT)三種類型。前兩種等價(jià)都可以表示串行隊(duì)列。而且有一個(gè)細(xì)節(jié)就是標(biāo)識(shí)符的格式是否有要求一時(shí)確定不了!

  • 往隊(duì)列里添加任務(wù)的方式一模一樣,都是dispatch_async(queue, ^{})這種格式。凡是創(chuàng)建的隊(duì)列都是以分線程的形式存在。所以對(duì)于一個(gè)分線程隊(duì)列來說就必須考慮如何返回到主線程。而且這也不是說純粹意義上的將這個(gè)分線程注銷,更多的是想將無法在分線程中執(zhí)行的代碼及時(shí)的傳遞到主線程以便進(jìn)行下一步操作。

  • 將分線程中下載完成的數(shù)據(jù)如何讓以參數(shù)的形式傳遞到主線程呢?兩種方法,方法1就是使用控制器的對(duì)象調(diào)用performSelectorOnMainThread方法。方法2就是通過dispatch_queue_t mainQueue = dispatch_get_main_queue()獲取到主隊(duì)列,這也就代表了主線程。通過在主隊(duì)列中添加任務(wù)也就等于是通過主線程來執(zhí)行分線程無法執(zhí)行的代碼。而且方法2還有一個(gè)優(yōu)點(diǎn)就是,無需像方法一那樣過于糾結(jié)于到底應(yīng)該將分線程的什么參數(shù)傳遞出去,因?yàn)榉椒ǘ侵苯咏⒃诜志€程的基礎(chǔ)上,本質(zhì)上只是起一個(gè)臨時(shí)調(diào)用主線程的功能,因此方法二可以直接調(diào)用分線程里的任何參數(shù)變量。

  • 如果不理解什么是串行隊(duì)列,其實(shí)主隊(duì)列dispatch_queue_t mainQueue = dispatch_get_main_queue()就是一個(gè)典型的串行隊(duì)列,無論你添加多少任務(wù)總是按照任務(wù)的添加順序依次執(zhí)行,沒有例外。而且自始至終都是一個(gè)線程,而且這個(gè)線程就叫做主線程。

不同點(diǎn)

  • 串行隊(duì)列無論添加多少個(gè)任務(wù)都只是創(chuàng)建了一個(gè)分線程,而且執(zhí)行任務(wù)的順序嚴(yán)格按照添加任務(wù)的先后順序進(jìn)行。而對(duì)于并行隊(duì)列而言,往并行對(duì)列中每增添一個(gè)任務(wù)就創(chuàng)建了一個(gè)分線程。也就是說并行隊(duì)列中有多少個(gè)任務(wù)就有多少個(gè)分線程,我們也都知道,線程其實(shí)本質(zhì)上也是一個(gè)對(duì)象,是對(duì)象就必須開辟內(nèi)存,因此雖然說創(chuàng)建很多的分線程有利于獲得良好的用戶體驗(yàn),但這對(duì)于用戶終端的內(nèi)存是一個(gè)極大的考驗(yàn)。

  • 并行隊(duì)列的任務(wù)因?yàn)槭且粋€(gè)任務(wù)對(duì)應(yīng)一個(gè)分線程。所以可以認(rèn)為并行隊(duì)列里的任務(wù)是同時(shí)進(jìn)行的。所以最終并行隊(duì)列里的哪一個(gè)任務(wù)最先執(zhí)行結(jié)束的關(guān)鍵還在于任務(wù)的具體內(nèi)容。

  • 對(duì)于并行隊(duì)列而言有一種很特殊的需求就是:我想準(zhǔn)確地知道并行隊(duì)列里的最后一個(gè)任務(wù)是什么時(shí)候執(zhí)行完畢,然后我再進(jìn)行下一部操作,因?yàn)橥ǔG闆r下是無法準(zhǔn)確地獲取到現(xiàn)在并行隊(duì)列的任務(wù)到底執(zhí)行到了一個(gè)什么階段。根本無法像串行隊(duì)列那樣通過添加任務(wù)的先后順序來推斷出到底哪一個(gè)任務(wù)結(jié)束后標(biāo)志著隊(duì)列任務(wù)全部結(jié)束!解決方案就是給并行隊(duì)列添加一個(gè)group。同樣需要通過dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT)先創(chuàng)建一個(gè)并行隊(duì)列,然后通過dispatch_group_t group = dispatch_group_create()創(chuàng)建一個(gè)管理并行隊(duì)列里所有任務(wù)的組。接著跟常規(guī)添加任務(wù)到并行隊(duì)列不同的是:dispatch_group_async(group, queue, ^{});當(dāng)然從分線程返回到主線程的方法dispatch_async(dispatch_get_main_queue(), ^{});并沒有發(fā)生變化。把所有的任務(wù)添加到有管理組的并行隊(duì)列中目的就是時(shí)刻監(jiān)聽并行隊(duì)列里的任務(wù)的進(jìn)行階段,監(jiān)聽到并行隊(duì)列里所有的任務(wù)全部結(jié)束時(shí)就會(huì)觸發(fā)方法dispatch_group_notify(group,dispatch_get_main_queue(),^{});需要注意的是,這個(gè)方法里面的代碼全部是由主線程執(zhí)行。因?yàn)椴⑿嘘?duì)列里的所有任務(wù)所對(duì)應(yīng)的分線程也已經(jīng)結(jié)束。監(jiān)聽到任務(wù)結(jié)束自動(dòng)將監(jiān)聽方法里面的代碼交由主線程執(zhí)行。這也就是說,其實(shí)管理并行隊(duì)列里所有任務(wù)的組group的監(jiān)聽方法其實(shí)本質(zhì)上就是就是并行隊(duì)列里所有任務(wù)都結(jié)束后返回到主線程以便進(jìn)行下一步操作。

  • 如果我們想要?jiǎng)?chuàng)建多個(gè)并行對(duì)列,那么又如何確定哪一個(gè)并行隊(duì)列率先執(zhí)行呢,答案是,根據(jù)并行隊(duì)列的優(yōu)先級(jí)來進(jìn)行判斷。創(chuàng)建有優(yōu)先級(jí)的并行隊(duì)列的方法為dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)。當(dāng)然在這里將并行隊(duì)列的優(yōu)先級(jí)設(shè)置為了默認(rèn),也就相當(dāng)于又增加了一種創(chuàng)建普通并行隊(duì)列的方法。

  • 如果我想隊(duì)列里的任務(wù)在多少秒之后才開始執(zhí)行,那么最簡單的方法就是在具體的任務(wù)里添加睡眠延時(shí)sleep(1),但是的但是這也僅僅適用于串行隊(duì)列中,而且就算這樣可以也會(huì)帶來一個(gè)整體任務(wù)進(jìn)行的延時(shí)。對(duì)于并行隊(duì)列來說,只要添加完了任務(wù)就會(huì)全面開始,根本不會(huì)停下來,所以最好的方法就是通過dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 4ull*NSEC_PER_MSEC)來設(shè)置過4秒后再通過dispatch_after(time, queue, ^{})將任務(wù)添加到隊(duì)列queue中。這樣就可以完美的實(shí)現(xiàn)至少在多少秒后再進(jìn)行某一個(gè)任務(wù)。6、無論在串行隊(duì)列還是在并行隊(duì)列中,有時(shí)候我們想要某個(gè)任務(wù)只進(jìn)行一次,就算這是一個(gè)死循環(huán)的隊(duì)列,也要保證某個(gè)任務(wù)只被執(zhí)行一次,方法就是先用static dispatch_once_t onceTaken確定謂詞onceTaken ,用來保證dispatch_once(&onceTaken,^{ })方法里的代碼也就是任務(wù)將從始至終只會(huì)被執(zhí)行一次。

互斥鎖

@synchronized(鎖對(duì)象) { // 需要鎖定的代碼  }

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的CPU資源
前提:多條線程搶奪同一塊資源
線程同步:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))

atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic),線程安全,需要消耗大量的資源
nonatomic:非原子屬性,不會(huì)為setter方法加鎖,非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備,盡量避免多線程搶奪同一塊資源,盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動(dò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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 文章目錄GCD簡介任務(wù)和隊(duì)列GCD的使用步驟隊(duì)列的創(chuàng)建方法任務(wù)的創(chuàng)建方法GCD的基本使用并行隊(duì)列 + 同步執(zhí)行并行...
    lusen_b閱讀 298評(píng)論 0 1
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時(shí)候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,416評(píng)論 21 42
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,918評(píng)論 1 17
  • 目錄 一、基本概念1.多線程2.串行和并行, 并發(fā)3.隊(duì)列與任務(wù)4.同步與異步5.線程狀態(tài)6.多線程方案 二、GC...
    BohrIsLay閱讀 1,712評(píng)論 5 12
  • 被別人冠名老實(shí)人這個(gè)代名詞,往往在生活或者工作中處于被動(dòng)狀態(tài),什么事情都可以,都好商量,沒有什么主心骨,長此...
    劉清紅閱讀 168評(píng)論 0 0

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