iOS多線程知識(shí)總結(jié)

我只是一個(gè)搬運(yùn)工萎胰,僅僅為了加深記憶,感謝作者分享棚辽,文章大部分來(lái)源:尚大大o_O

線程和進(jìn)程


幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù)技竟,一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)程序就是一個(gè)進(jìn)程屈藐。當(dāng)一個(gè)程序運(yùn)行時(shí)榔组,內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程联逻。

  • 進(jìn)程(Process )

當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行后搓扯,即變成一個(gè)進(jìn)程。進(jìn)程是處于是處于運(yùn)行過(guò)程中的程序包归,并且具有一定的獨(dú)立功能锨推,進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。一般而言公壤,進(jìn)程有如下特征:

  1. 獨(dú)立性:有自己獨(dú)立的資源换可,且擁有自己私有的地址空間。在沒(méi)有經(jīng)過(guò)進(jìn)程本省的允許下厦幅,其他進(jìn)程是不能直接訪問(wèn)其進(jìn)程的地址空間的沾鳄。
  2. 動(dòng)態(tài)性:程序只是靜態(tài)的指令集合,而進(jìn)程是一個(gè)正在系統(tǒng)中活動(dòng)的指令集合确憨。進(jìn)程有時(shí)間的概念译荞,具有自己的生命周期和各種狀態(tài)。
  3. 并發(fā)性:多個(gè)進(jìn)程可以在單個(gè)處理器上并發(fā)執(zhí)行休弃,互相不會(huì)影響吞歼。
  • 線程(Thread)

線程也被稱做輕量級(jí)進(jìn)程,線程是進(jìn)程的執(zhí)行單元玫芦。就像進(jìn)程在系統(tǒng)中一樣浆熔,線程在進(jìn)程中也是獨(dú)立的,并發(fā)的執(zhí)行流程。一個(gè)進(jìn)程可以擁有多個(gè)線程医增,一個(gè)線程必須有一個(gè)父進(jìn)程慎皱,但不再擁有系統(tǒng)資源,而是和父進(jìn)程一起共享父進(jìn)程的全部資源叶骨。多線程由于共享父進(jìn)程的資源茫多,所以編程更加方便,但是也需要小心線程不會(huì)影響到父進(jìn)程中的其他線程忽刽。線程是獨(dú)立運(yùn)行的天揖,它并不知道其他線程的存在。線程執(zhí)行是搶占式的跪帝,也就是說(shuō)今膊,當(dāng)前運(yùn)行的線程在任何時(shí)候都可能被掛起,以便林另外一個(gè)線程可以運(yùn)行伞剑。

  • 多線程優(yōu)點(diǎn)
  1. 進(jìn)程間不可以共享內(nèi)存斑唬,但線程之間共享內(nèi)存十分容易。
  2. 系統(tǒng)創(chuàng)建進(jìn)程需要為其重新分配系統(tǒng)資源黎泣,但是創(chuàng)建線程代價(jià)小得多恕刘,因此效率更高

為什么要用多線程編程


為了提高資源利用率來(lái)提升系統(tǒng)整體效率,實(shí)際往往是將耗時(shí)操作放在后臺(tái)執(zhí)行抒倚,避免阻塞主線程褐着,在iOS中UI繪制和用戶響應(yīng)都是主線程。

NSThread


常用API

- (void)viewDidLoad {
    [super viewDidLoad];

    //打印當(dāng)前線程
    NSLog(@"開(kāi)始:%@   優(yōu)先級(jí):%d", [NSThread currentThread], [NSThread currentThread].qualityOfService);

    //1.創(chuàng)建NSTread對(duì)象托呕,必須調(diào)用start方法開(kāi)始含蓉,并且只能傳一個(gè)參數(shù)object
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"test"];
    //    NSThread *thread = [[NSThread alloc] initWithBlock:^{}];
    thread.name = @"testThread";
    thread.qualityOfService = NSQualityOfServiceUserInteractive;
    [thread start];

    //2.直接創(chuàng)建并啟動(dòng)線程
    //    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"];
    //    [NSThread detachNewThreadWithBlock:^{}];

    //3.隱式直接創(chuàng)建
//    [NSThread performSelectorInBackground:@selector(run:) withObject:nil];

    //    NSLog(@"結(jié)束:%@", [NSThread currentThread]);
}

- (void)run:(NSObject *)object {
    //阻塞休眠
    //    [NSThread sleepForTimeInterval:5];
    //中止當(dāng)前線程
    //    [NSThread exit];
    NSLog(@"子線程運(yùn)行:%@ %@  優(yōu)先級(jí):%d", [NSThread currentThread], object, [NSThread currentThread].qualityOfService);
}
復(fù)制代碼
  • 線程的狀態(tài)

線程被啟動(dòng)后,并不是直接進(jìn)入執(zhí)行狀態(tài)项郊,也不是一直處于執(zhí)行狀態(tài)谴餐,由于線程并發(fā),線程會(huì)反復(fù)在運(yùn)行呆抑、就緒間切換。創(chuàng)建一個(gè)線程后汁展,處于新建狀態(tài)鹊碍,系統(tǒng)為其分配內(nèi)存,初始化成員變量食绿;調(diào)用-(void)start侈咕;方法后,該線程處于就緒狀態(tài)器紧,系統(tǒng)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器耀销,此時(shí)并沒(méi)有運(yùn)行,何時(shí)運(yùn)行取決于系統(tǒng)調(diào)度铲汪。

[圖片上傳中...(image-ce30cc-1577264887488-0)]

<figcaption></figcaption>

  • 終止子線程

每個(gè)線程都有一定的優(yōu)先級(jí)熊尉,優(yōu)先級(jí)越高獲得執(zhí)行機(jī)會(huì)越多罐柳。目前通過(guò)qualityOfService屬性來(lái)設(shè)置,原來(lái)的threadPriority由于語(yǔ)義不夠清晰狰住,已經(jīng)被廢棄了张吉。

NSQualityOfServiceUserInteractive:最高優(yōu)先級(jí),主要用于提供交互UI的操作催植,比如處理點(diǎn)擊事件肮蛹,繪制圖像到屏幕上
NSQualityOfServiceUserInitiated:次高優(yōu)先級(jí),主要用于執(zhí)行需要立即返回的任務(wù)
NSQualityOfServiceDefault:默認(rèn)優(yōu)先級(jí)创南,當(dāng)沒(méi)有設(shè)置優(yōu)先級(jí)的時(shí)候伦忠,線程默認(rèn)優(yōu)先級(jí)
NSQualityOfServiceUtility:普通優(yōu)先級(jí),主要用于不需要立即返回的任務(wù)
NSQualityOfServiceBackground:后臺(tái)優(yōu)先級(jí)稿辙,用于完全不緊急的任務(wù)
復(fù)制代碼
  • 缺點(diǎn)

使用NSThread進(jìn)行多線程編程較復(fù)雜昆码,需要自己控制多線程的同步、并發(fā)邓深,還需要自己控制線程的終止銷毀未桥,稍有不留神容易出現(xiàn)錯(cuò)誤,對(duì)開(kāi)發(fā)者要求較高芥备,一般較少使用冬耿。

NSOperation


iOS還提供了NSOperation與NSOperationQueue來(lái)實(shí)現(xiàn)多線程,是基于GCD更高一層的封裝萌壳,完全面向?qū)ο笠嘞狻5荊CD更簡(jiǎn)單易用、代碼可讀性也更高袱瓮。

NSOperationQueue:負(fù)責(zé)管理系統(tǒng)提交的多個(gè)NSOperation缤骨,底層維護(hù)了一個(gè)線程池。不同于GCD中的調(diào)度隊(duì)列FIFO(先進(jìn)先出)原則尺借。NSOperationQueue對(duì)于添加到隊(duì)列中的操作绊起,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開(kāi)始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)燎斩。

NSOperation: 代表一個(gè)多線程任務(wù)虱歪。

  • 為什么要使用NSOperation、NSOPerationQueue栅表?
  1. 可以添加完成的代碼塊笋鄙,在操作完成后執(zhí)行。
  2. 添加操作之間的依賴關(guān)系怪瓶,方便的控制執(zhí)行順序萧落。
  3. 設(shè)定操作執(zhí)行的優(yōu)先級(jí)。
  4. 可以很方便的取消一個(gè)操作的執(zhí)行。
  5. 使用KVO觀察對(duì)操作執(zhí)行狀態(tài)的更改:isExecuteing找岖、isFinished陨倡、isCancelled。
  • 常用API
    NSOperationQueue *queue;
    //獲取執(zhí)行當(dāng)前NSOperation的NSOperationQueue隊(duì)列
    //    queue = [NSOperationQueue currentQueue];
    //獲取主線程的NSOperationQueue隊(duì)列
    //    queue = [NSOperationQueue mainQueue];
    //自定義隊(duì)列
    queue = [[NSOperationQueue alloc] init];
    //隊(duì)列名
    queue.name = @"testOperationQueue";
    //最大并發(fā)操作數(shù)(系統(tǒng)有限制宣增,即使設(shè)置很大玫膀,也會(huì)自動(dòng)調(diào)整)
    queue.maxConcurrentOperationCount = 10;
    //設(shè)置優(yōu)先級(jí)
    queue.qualityOfService = NSQualityOfServiceDefault;

    //自定義NSOperation,如果SEL和Block為空爹脾,系統(tǒng)不會(huì)加入到指定隊(duì)列
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation");
    }];
    //添加依賴關(guān)系帖旨,invocationOperation執(zhí)行完后才執(zhí)行blockOperation
    [blockOperation addDependency:invocationOperation];
    //添加到隊(duì)列中
    //    [queue addOperation:invocationOperation];
    [queue addOperations:@[invocationOperation, blockOperation] waitUntilFinished:NO];
    //直接添加代碼塊任務(wù)
    [queue addOperationWithBlock:^{

    }];

    //打印所有的NSOperation
    for(int i=0; i<queue.operationCount; i++) {
        NSLog(@"隊(duì)列%@的第%d個(gè)NSOperation:%@", queue.name, i, queue.operations[i]);
    }

    //終止所有NSOperation
    //    [queue cancelAllOperations];
    //執(zhí)行完所有NSOperation才能解除阻塞當(dāng)前線程
    //    [queue waitUntilAllOperationsAreFinished];
復(fù)制代碼

GCD(Grand Central Dispatch)

  • 基本概念
  1. 隊(duì)列:隊(duì)列負(fù)責(zé)開(kāi)發(fā)者提交的任務(wù),不過(guò)不同任務(wù)的執(zhí)行時(shí)間不一樣灵妨,先處理的任務(wù)不一定先完成解阅。隊(duì)列即可是串行的,也可是并行的泌霍,隊(duì)列底層會(huì)維持一個(gè)線程池來(lái)處理任務(wù)货抄,串行隊(duì)列只需要維護(hù)一個(gè)線程即可,并行隊(duì)列則需要維護(hù)多個(gè)線程朱转。
  2. 任務(wù):用戶提交給隊(duì)列的工作單元蟹地,這些任務(wù)將會(huì)提交給隊(duì)列底層維護(hù)的線程池。
  3. 異步:可以在新的線程中執(zhí)行任務(wù)藤为,但不一定會(huì)開(kāi)辟新的線程怪与。dispatch函數(shù)會(huì)立即返回,然后Block在后臺(tái)異步執(zhí)行缅疟。
  4. 同步:在當(dāng)前線程執(zhí)行任務(wù)分别,不會(huì)開(kāi)辟新的線程。必須等到Block函數(shù)執(zhí)行完畢后存淫,dispatch函數(shù)才會(huì)返回耘斩。

注:隊(duì)列的串行和并行決定了任務(wù)以何種方式執(zhí)行,執(zhí)行的異步和同步?jīng)Q定了是否需要開(kāi)辟新線程處理任務(wù)桅咆。

  • 特點(diǎn)
  1. GCD可用于多核的并行運(yùn)算括授;
  2. GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)岩饼;
  3. GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程刽脖、調(diào)度任務(wù)、銷毀線程)忌愚;
  4. 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要寫(xiě)任何線程管理代碼却邓;
  • 常用API
    /** 獲取隊(duì)列 */
    //獲取指定優(yōu)先級(jí)的全局并發(fā)隊(duì)列(flag填0即可硕糊,僅預(yù)留的參數(shù),使用其他值可能會(huì)返回null)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //創(chuàng)建自定義并行隊(duì)列
    dispatch_queue_t queue1 = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_CONCURRENT);
    //獲取系統(tǒng)主線程關(guān)聯(lián)的串行隊(duì)列
    dispatch_queue_t queue2 = dispatch_get_main_queue();
    //創(chuàng)建自定義串行隊(duì)列
    dispatch_queue_t queue3 = dispatch_queue_create("testQueue3", DISPATCH_QUEUE_SERIAL);

    /** 提交任務(wù) */
    //異步提交代碼塊到并發(fā)隊(duì)列
    dispatch_async(queue, ^{

    });
    //同步提交代碼塊到自定義并發(fā)隊(duì)列
    dispatch_sync(queue1, ^{

    });

    //異步提交代碼塊到串行隊(duì)列,線程池將在指定時(shí)間執(zhí)行代碼塊(實(shí)際是5秒后加入到隊(duì)列中简十,實(shí)際并不一定會(huì)立馬執(zhí)行檬某,一般精度要求下是沒(méi)問(wèn)題的)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5*NSEC_PER_SEC)), queue2, ^{

    });

    //異步提交代碼到自定義串行隊(duì)列,同步函數(shù)螟蝙,無(wú)論是在串行還是并行隊(duì)列中執(zhí)行恢恼,都要執(zhí)行完才返回,所以要防止線程阻塞和死鎖胰默,time表示當(dāng)前是第幾次(如果提交給并發(fā)隊(duì)列场斑,會(huì)啟動(dòng)五個(gè)線程來(lái)執(zhí)行)
    dispatch_apply(5, queue3, ^(size_t time) {

    });

    //實(shí)際是個(gè)long類型變量,用于判斷該代碼塊是否被執(zhí)行過(guò)
    static dispatch_once_t onceToken; 
    //主線程執(zhí)行一次代碼塊
    dispatch_once(&onceToken, ^{

    });

    //等group執(zhí)行完后牵署,才能執(zhí)行下一步
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    /** 組(用于需要等待多個(gè)任務(wù)全部執(zhí)行完再進(jìn)行下一步) */
    dispatch_group_t group = dispatch_group_create();

    //并發(fā)執(zhí)行的代碼塊1
    dispatch_group_async(group, queue, ^{

    });

    //并發(fā)執(zhí)行的代碼塊2
    dispatch_group_async(group, queue, ^{

    });

    //等待兩個(gè)代碼塊執(zhí)行完匯總
    dispatch_group_notify(group, queue, ^{

    });

    /** 柵欄(用于需要依次執(zhí)行完多個(gè)線程組) */
    //并發(fā)隊(duì)列異步執(zhí)行代碼塊1漏隐,2
    dispatch_async(queue, ^{
        //代碼塊1
    });
    dispatch_async(queue, ^{
        //代碼塊2
    });
    //1,2執(zhí)行完后才會(huì)執(zhí)行3奴迅,4
    dispatch_barrier_async(queue, ^{

    });
    //并發(fā)隊(duì)列異步執(zhí)行代碼塊3青责,4
    dispatch_async(queue, ^{
        //代碼塊3
    });
    dispatch_async(queue, ^{
        //代碼塊4
    });

    /** 信號(hào)量(用于控制線程的等待和執(zhí)行) */
    //創(chuàng)建信號(hào)量,value表示初始信號(hào)總量取具,支持多少個(gè)操作來(lái)執(zhí)行
    dispatch_semaphore_t t = dispatch_semaphore_create(1);
    //發(fā)送一個(gè)信號(hào)脖隶,讓信號(hào)總量+1
    dispatch_semaphore_signal(t);
    //使信號(hào)總量-1,如果總量為0暇检,則會(huì)一直等待(阻塞所在線程)产阱,直到總量大于0則繼續(xù)執(zhí)行
    dispatch_semaphore_wait(t, DISPATCH_TIME_FOREVER);

    /*1.可以將異步執(zhí)行變?yōu)橥綀?zhí)行,如需要等待下載完后再直接返回?cái)?shù)據(jù)(我們也可以通過(guò)block回調(diào))*/
    //總信號(hào)量設(shè)置為0
    dispatch_semaphore_t t1 = dispatch_semaphore_create(0);
    //執(zhí)行耗時(shí)代碼
    void (^downloadTask)(void) = ^ {
        //下載圖片
        ...
        ...
        //完成后發(fā)送信號(hào)量
        dispatch_semaphore_signal(t1);
    };
    downloadTask();
    //一直等到信號(hào)量計(jì)數(shù)為1才執(zhí)行下一步占哟,也就是等到圖片下載完后
    dispatch_semaphore_wait(t1, DISPATCH_TIME_FOREVER);

    /*2.保證線程安全*/
    //設(shè)置信號(hào)量初始計(jì)數(shù)為1心墅,保證只能有一個(gè)操作能進(jìn)來(lái)
    dispatch_semaphore_t t2 = dispatch_semaphore_create(1);
    //相當(dāng)于加鎖,消耗使用計(jì)數(shù)榨乎,如果已經(jīng)被一個(gè)線程使用怎燥,后續(xù)只能掛起等待信號(hào)量回復(fù)
    dispatch_semaphore_wait(t2, DISPATCH_TIME_FOREVER);
    //執(zhí)行業(yè)務(wù)代碼
    ...
    ...
    //解鎖
    dispatch_semaphore_signal(t2);

    /*3.模擬NSOperationQueue的最大并發(fā)操作數(shù)*/
    //最大并發(fā)操作支持10
    dispatch_semaphore_t t3 = dispatch_semaphore_create(10);
    //剩余操作同上,其實(shí)就是類似于將NSOperationQueue的maxConcurrentOperationCount設(shè)置為10
復(fù)制代碼
  • 后臺(tái)運(yùn)行

在App程序進(jìn)入后臺(tái)時(shí)蜜暑,我們應(yīng)該盡量釋放內(nèi)存和保存用戶數(shù)據(jù)或者狀態(tài)信息铐姚。在默認(rèn)情況下,應(yīng)該僅在5秒鐘處理這些工作肛捍,我們可以通過(guò)UIApplicationbeginBackgroundTaskWithExpirationHandler方法來(lái)申請(qǐng)延長(zhǎng)處理時(shí)間隐绵,最多有十分鐘。

- (void)applicationDidEnterBackground:(UIApplication *)application {
    //聲明關(guān)閉后臺(tái)任務(wù)代碼塊
    void (^endBackgroundTask)(UIBackgroundTaskIdentifier backgroudTask) = ^(UIBackgroundTaskIdentifier backgroudTask) {
        [[UIApplication sharedApplication] endBackgroundTask:backgroudTask];
        backgroudTask = UIBackgroundTaskInvalid;
    };

    //開(kāi)啟后臺(tái)任務(wù)
    __block UIBackgroundTaskIdentifier backgroudTask;
    backgroudTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        //十分鐘內(nèi)仍然沒(méi)有完成拙毫,系統(tǒng)處理終止句柄
        endBackgroundTask(backgroudTask);
    }];

    //執(zhí)行相關(guān)代碼

    //結(jié)束后臺(tái)任務(wù)
    endBackgroundTask(backgroudTask);
}
復(fù)制代碼
  • 線程死鎖
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
}
復(fù)制代碼

在主隊(duì)列中增加同步代碼塊依许,就會(huì)造成死鎖,由于同步是需要立即順序執(zhí)行的缀蹄,上述代碼中峭跳,Block中的方法需要在viewDidLoad結(jié)束后才能完成膘婶,但是viewDidLoad想要結(jié)束又必須先結(jié)束B(niǎo)lock中的方法,所以相互永久等待蛀醉,造成了死鎖悬襟。

GCD會(huì)造成循環(huán)引用嗎?

直接使用GCD的相關(guān)API一般是不會(huì)的拯刁,block結(jié)束后沒(méi)有循環(huán)引用的條件脊岳,YYKit的issues下有個(gè)有去的討論:dispatch_async的block里面需要_weak self嗎?

  • 注意
  1. 同步執(zhí)行會(huì)在當(dāng)前線程執(zhí)行任務(wù)垛玻,不具有開(kāi)辟線程的能力或者說(shuō)沒(méi)有必要開(kāi)辟新的線程割捅。并且,同步執(zhí)行必須等到Block函數(shù)執(zhí)行完畢夭谤,dispatch函數(shù)才會(huì)返回棺牧,從而阻塞同一串行隊(duì)列中外部方法的執(zhí)行。
  2. 異步執(zhí)行dispatch函數(shù)會(huì)直接返回朗儒,只有異步執(zhí)行才有開(kāi)辟新線程的必要颊乘,但是異步執(zhí)行不一定會(huì)開(kāi)辟新線程。
  3. 想要開(kāi)辟新線程必須讓任務(wù)在異步執(zhí)行醉锄,想要開(kāi)辟多個(gè)線程乏悄,只有讓任務(wù)在并行隊(duì)列中異步執(zhí)行才可以。執(zhí)行方式和隊(duì)列類型多層組合在一定程度上能夠?qū)崿F(xiàn)對(duì)于代碼執(zhí)行順序的調(diào)度恳不。
  4. 同步+串行:未開(kāi)辟新線程檩小,串行執(zhí)行任務(wù);同步+并行:未開(kāi)辟新線程烟勋,串行執(zhí)行任務(wù)规求;異步+串行:新開(kāi)辟一條線程,串行執(zhí)行任務(wù)卵惦;異步+并行:開(kāi)辟多條新線程阻肿,并行執(zhí)行任務(wù);在主線程中同步使用主隊(duì)列執(zhí)行任務(wù)沮尿,會(huì)造成死鎖丛塌。

線程安全

線程安全主要是由于系統(tǒng)的線程調(diào)度具有一定的隨機(jī)性造成的,由于是多并發(fā)畜疾,多個(gè)線程同時(shí)對(duì)一份數(shù)據(jù)進(jìn)行讀寫(xiě)赴邻,就可能在讀取執(zhí)行一般的時(shí)候另外一個(gè)線程去寫(xiě)入,導(dǎo)致數(shù)據(jù)異常啡捶。線程安全即保證線程同步

  • 線程安全的類的特征
  1. 該類的對(duì)象可以被多個(gè)線程安全訪問(wèn)姥敛。
  2. 每個(gè)線程調(diào)用對(duì)象的任意方法都會(huì)得到正確的結(jié)果。
  3. 每個(gè)線程調(diào)用對(duì)象的任意方法之后瞎暑,該對(duì)象仍保持合理狀態(tài)彤敛。
  • @synchronized是對(duì)mutex遞歸鎖的封裝

為了解決這個(gè)問(wèn)題忿偷,Objective-C的多線程支持引入同步,使@synchronized修飾代碼塊臊泌,被修飾的代碼塊可簡(jiǎn)稱為同步代碼塊,語(yǔ)法格式如下

@synchronized (obj) {
    //同步代碼塊
}
復(fù)制代碼

其中obj就是同步監(jiān)視器揍拆,當(dāng)一個(gè)線程執(zhí)行同步前渠概,必須先獲得同步監(jiān)視器的鎖定,任何時(shí)刻只能有一個(gè)線程獲得鎖定嫂拴,執(zhí)行完成后播揪,才會(huì)釋放,如果此時(shí)有新的線程訪問(wèn)筒狠,那么新線程會(huì)進(jìn)入休眠狀態(tài)猪狈。通常推薦使用可能被并發(fā)訪問(wèn)的共享資源作為同步監(jiān)視器。

iOS中的鎖


1. OSSpinLock(自旋鎖)

  • 等待鎖的線程處于忙等(busy-wait)狀態(tài)辩恼,一直占用著CPU資源雇庙;
  • 目前已經(jīng)不再安全,可能會(huì)出現(xiàn)優(yōu)先級(jí)翻轉(zhuǎn)問(wèn)題灶伊;
  • 如果等待鎖的線程優(yōu)先級(jí)較高疆前,它會(huì)一直占用著CPU資源,優(yōu)先級(jí)低的線程就無(wú)法釋放鎖聘萨;
  • 需要導(dǎo)入頭文件#import <libkern/OSatomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//嘗試加鎖(如果需要等待就不加鎖竹椒,直接返回false;如果不需要等待加鎖米辐,返回true)
bool resule = OSSpinLockTry(&lock);
//加鎖
OSSpinLock(&lock);
//解鎖
OSSpinLockUnlock(&lock);
復(fù)制代碼

2. os_unfair_lock

  • 用于取代不安全的OSSpinLock胸完,從iOS10開(kāi)始支持;
  • 從底層調(diào)用看翘贮,等待os_unfair_locks鎖的線程會(huì)處于休眠狀態(tài)赊窥,并非忙等;
  • 需要導(dǎo)入頭文件#import <os/lock.h>
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//嘗試加鎖
os_unfair_lock_trylock(&lock);
//加鎖
os_unfair_lock_lock(&lock);
//解鎖
os_unfair_lock_unlock(&lock);
復(fù)制代碼

3. pthread_mutex

互斥鎖

  • mutex叫做“互斥鎖”择膝,等待的線程會(huì)處于休眠狀態(tài)
  • 需要導(dǎo)入頭文件#import <pthread.h>
//初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL);
//初始化
pthread_mutex_t mutex;
pthread_mutex_init (&mutex,&attr);
//嘗試加鎖
pthread_mutex_trylock (&mutex);
//加鎖
pthread_mutex_lock (&mutex);
//解鎖
pthread_mutex_unlock (&mutex);
//銷毀相關(guān)資源
pthread_mutexattr_unlock(&attr)誓琼;
pthread_mutex_destroy(&mutex);
復(fù)制代碼

遞歸鎖

  • 遞歸鎖:允許同一個(gè)線程對(duì)一把鎖進(jìn)行重復(fù)加鎖
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(mutex, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
復(fù)制代碼

條件

 // 初始化鎖
pthread_mutex_t mutex;
//NULL代表使用默認(rèn)屬性
pthread_mutex_init(&mutex, NULL);
// 初始化條件
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
//等待條件(進(jìn)入休眠肴捉,放開(kāi)mutex鎖腹侣;被喚醒后,會(huì)再次對(duì)mutex加鎖)
pthread_cond_wait(&cond, &mutex);
//激活一個(gè)等待條件的線程
pthread_cond_signal(&cond);
//銷毀資源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
復(fù)制代碼

4. NSLock齿穗、NSRecursiveLock

  • NSLock是對(duì)mutex普通鎖的封裝
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking>
{
- (BOOL)tryLock;
- (BOOl)lockBeforeDate:(NSDate *)limit;
}
@end

//初始化鎖
NSLock *lock = [[NSLock alloc] init];
復(fù)制代碼
  • NSRecursiveLock也是對(duì)mutex遞歸所得封裝傲隶,API跟NSLock基本一致。

5. NSCondition

  • NScondition 是對(duì)mutex和cond的封裝
@interface NSCondition : NSObject <NSLocking>
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
復(fù)制代碼

6. NSConditionLock

  • NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝窃页,可以設(shè)置具體的條件值
@interface NSConditionLock : NSObject <NSLocking> {
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
}
@end
復(fù)制代碼

7. dispatch_semaphore

  • semaphore叫做信號(hào)量跺株;
  • 信號(hào)量的初始值复濒,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量;
  • 信號(hào)量的初始值為1乒省,代表同時(shí)只允許1條線程訪問(wèn)資源巧颈,保證線程同步
//信號(hào)量的初始值
int value = 1;
//初始化信號(hào)量
dispatch_semaphore semephore = dispatch_semaphore_creat(value);
//如果信號(hào)量的值<=0袖扛,當(dāng)前線程就會(huì)進(jìn)入休眠等待(直到信號(hào)量的值>0)
//如果信號(hào)量的值>0, 就減1跷睦,然后往下執(zhí)行后面的代碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//讓信號(hào)量的值加1
dispatch_semaphore_signal(semaphore)铅搓;
復(fù)制代碼

8. dispatch_queue

  • 直接使用GCD的串行隊(duì)列,也是可以實(shí)現(xiàn)線程同步的
dispatch_queue_t queue = dispatch_queue_creat("lock_queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//任務(wù)
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市号醉,隨后出現(xiàn)的幾起案子剧罩,更是在濱河造成了極大的恐慌固翰,老刑警劉巖收厨,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異砸讳,居然都是意外死亡琢融,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)绣夺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吏奸,“玉大人,你說(shuō)我怎么就攤上這事陶耍》芪担” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵烈钞,是天一觀的道長(zhǎng)泊碑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)毯欣,這世上最難降的妖魔是什么馒过? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮酗钞,結(jié)果婚禮上腹忽,老公的妹妹穿的比我還像新娘。我一直安慰自己砚作,他們只是感情好窘奏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著葫录,像睡著了一般着裹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上米同,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天骇扇,我揣著相機(jī)與錄音摔竿,去河邊找鬼。 笑死少孝,一個(gè)胖子當(dāng)著我的面吹牛继低,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稍走,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郁季,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钱磅?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤似枕,失蹤者是張志新(化名)和其女友劉穎盖淡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凿歼,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪迟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了答憔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味赃。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虐拓,靈堂內(nèi)的尸體忽然破棺而出心俗,到底是詐尸還是另有隱情,我是刑警寧澤蓉驹,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布城榛,位于F島的核電站,受9級(jí)特大地震影響态兴,放射性物質(zhì)發(fā)生泄漏狠持。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一瞻润、第九天 我趴在偏房一處隱蔽的房頂上張望喘垂。 院中可真熱鬧,春花似錦绍撞、人聲如沸正勒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昭齐。三九已至,卻和暖如春矾柜,著一層夾襖步出監(jiān)牢的瞬間阱驾,已是汗流浹背就谜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留里覆,地道東北人丧荐。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喧枷,于是被迫代替她去往敵國(guó)和親虹统。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容