IOS多線程—GCD由淺入深總結

注意幾個名詞:

同步:不會開啟子線程, 而且會阻塞當前線程
異步:不會阻塞當前線程, 且具備開啟線程的能力(不一定開啟)
任務:block里面的代碼塊
串行隊列:只會開啟一個子線程
并行隊列:可以開啟多個子線程,但是不一定一定開啟線程,因為開啟一個線程會:消耗CPU資源、占內存(子線程512kb,主線程1M)能開啟幾個線程取決于內核,一般開啟3-6個就可以了
主隊列:異步添加任務必須等到主線程中的任務執行完才執行,就是一個串行隊列,但是在主隊列中添加的異步任務不會開啟子線程

demo

第一部分:基礎

一、同步串行

//同步串行
/*
 同步: 看到這兩個字首先想到的是不會開啟子線程,而且會阻塞當前線程
 */

- (void)syncSerial{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3333");
    });
    dispatch_sync(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

二、異步串行

//異步串行
/*
 異步: 要想到的是不會阻塞當前線程,且具備開啟線程的能力(不一定會創建子線程,例如在串行隊列中,只會創建一條子線程)
 */
- (void)asyncSerial{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

三、異步并行

//異步并行
- (void)asyncConcurrent{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

四、同步并行

//同步并行
- (void)syncConcurrent{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3333");
    });
    dispatch_sync(queue, ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

第二部分:嵌套

一、同步并行嵌套異步任務

//同步并行嵌套異步任務
- (void)syncConcurrentAsync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"2222");
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"3333");
        });
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

結果分析:
1、先執行11111
2、遇到sync,同步任務,不會開啟子線程,而且會阻塞當前線程,所以會執行2222
3、遇到aync,異步任務會開啟子線程,不會阻塞當前線程,所以 執行一下3333,但是不需要3333這個異步任務執行完(不會阻塞當前線程) 就可以執行4444
4、執行完4444后,因為3333延遲2秒,所以會執行5555(如果不延遲2秒的話,可能會先執行3333也可能會先執行5555)
5、2秒后執行3333

二、同步串行嵌套異步任務

//同步串行嵌套異步任務
- (void)syncSerialAsync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //block1:任務1
        NSLog(@"2222");
        dispatch_async(queue, ^{
            //block2:任務2
            NSLog(@"3333");
        });
        sleep(2);
        NSLog(@"4444");
    });
    sleep(2);
    NSLog(@"5555");
}

執行結果:

image.png

結果分析:

1、在串行隊列中,任務順序執行,必修等一個任務完成才能執行下一個任務,代碼中sync包含兩個任務:block1(2222,3333,4444)和block2(3333)
2、首先執行1111
3、遇到sync:同步任務,且在串行隊列中有兩個任務,順序為block1和block2,所以要先執行完block1才能執行block2,所以先執行2222
4、遇到block1中async:異步任務,開啟一個線程,不會阻塞當前線程,所以執行sleep和4444,因為3333是在block2中,所以會要把sleep和4444執行完(謹記sleep和4444在block1中),才會執行block2中的3333
5、兩個任務都執行完后,執行sleep和5555

三、異步串行嵌套同步任務

//異步串行嵌套同步任務
- (void)asyncSerialSync{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //block1:任務1
        NSLog(@"2222");
        dispatch_sync(queue, ^{
            //block2:任務2
            NSLog(@"3333");
        });
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:死鎖

image.png

結果分析:
1、先執行1111
2、遇到aync:異步任務,開啟子線程,不會阻塞當前線程,所以執行5555
3、在aync中先執行2222
4、遇到sync:同步任務,不會開啟新的子線程,且阻塞當前線程(2步驟中開啟的子線程),又因為任務block1和block2在串行隊列中,所以必須要等到任務block1(4444)執行完才能執行任務block2(3333),綜合剛才說的block2(3333)阻塞這個子線程(即必須等到block2(3333)完成才能繼續執行block1中的(4444)),進而造成死鎖。

四、主隊列中添加異步任務

//主隊列中添加異步任務
- (void)main{
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"2222");
    });
    sleep(2);
    NSLog(@"3333");
}

執行結果:

image.png

結果分析:
1、主隊列中異步添加任務必須等到主線程中的任務執行完才執行
2、主隊列就是一個串行隊列,但是在主隊列中添加的異步任務不會開啟子線程

五、主隊列中添加同步任務

//主隊列中添加同步任務
- (void)main1{
    //任務1
    NSLog(@"1111");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        //block任務2
        NSLog(@"2222");
    });
    NSLog(@"3333");
}

執行結果:死鎖

image.png

結果分析:
1、在主線程中有兩個任務:main1和block,先執行1111
2、遇到sync:同步任務,阻塞主線程
3、但是要想執行任務2(2222)必須要先執行完主線程中的任務,即main1(1111和3333)造成main1和block相互阻塞。

第三部分:常用api

一、dispatch_apply

- (void)apply{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"index=%ld線程:%@", index, [NSThread currentThread]);
    });
    NSLog(@"end");
}

執行結果:

image.png

結果分析:
dispatch_apply類似一個for循環,會在指定的隊列中中運行block任務n次,如果隊列是并發隊列,則會并發執行block任務。
dispatch_apply在串行隊列中按照順序執行,完全沒有意義。在并發隊列中創建了N個任務,但并非所有任務都開辟線程,也有在主線程中完成的。輸出end,因為dispatch_apply函數會等待所有的處理結束,才會向下執行。

二、dispatch_group_t

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1111");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2222");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"3333");
    });
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
}

執行結果

image.png

結果分析:
在group中的任務異步執行。等全部執行完畢后,通過dispatch_group_notify再執行后面代碼。

三、比較dispatch_apply和dispatch_group_t

通過前兩個api講述可以看到其功能很相似。
1、我在dispatch_apply中添加異步任務

- (void)apply{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(20, queue, ^(size_t index) {
        dispatch_async(queue, ^{
            NSLog(@"index=%ld線程:%@", index, [NSThread currentThread]);
        });
    });
    NSLog(@"end");
}

執行結果:

image.png

結果分析:
可以看出先執行end,在dispatch_apply中的異步任務均是在子線程中完成。

2、在dispatch_group_t中同樣添加異步任務

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1111");
        });
    });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2222");
        });  
  });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3333");
        });   
 });
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

結果分析:
可以看出,同dispatch_apply一樣,也不能保證group中全部完成再執行4444。

3、但是dispatch_group_t可以這么做來解決

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1111");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2222");
        });
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3333");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"4444");
    });
    NSLog(@"5555");
}

執行結果:

image.png

第四部分:線程鎖(線程安全)

線程不安全:內存數據被多個線程讀取之后,出現的結果不可預見。
線程鎖:確保在讀取數據時只有一條線程再操作。
這里總結三種鎖:synchronized、nslock、dispatch_semaphore_t、nonatomic和atomic的區別

先看下邊代碼

- (void)synchronized{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        
        NSLog(@"1111");
        sleep(2);
        NSLog(@"2222");
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");
        sleep(2);
        NSLog(@"4444");
    });
}

執行順序:

image.png

出現線程不安全,因此需要加鎖。

1、synchronized
- (void)synchronized{
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        @synchronized (self){
            NSLog(@"1111");
            sleep(2);
            NSLog(@"2222");
        }
    });
    dispatch_async(queue, ^{
        @synchronized (self){
            NSLog(@"3333");
            sleep(2);
            NSLog(@"4444");
        }
    });
    NSLog(@"5555");
}

執行結果:

image.png

結果分析:
等到一個線程完成,才會執行另一個線程。

2、dispatch_semaphore_t
/*
 信號量:控制我們的線程并發數
 */
- (void)semaphore{
    //線程并發數設為1,只有一個線程在走
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_queue_t queue = dispatch_queue_create("com.wanggang", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"1111");
        sleep(2);
        NSLog(@"2222");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"3333");
        sleep(2);
        NSLog(@"4444");
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"5555");
}

執行結果:

image.png
3、項目中為什么用nonatomic不用atomic

nonatomic:不安全
atomic:加鎖+耗性能

//有兩個屬性,分別設置為nonatomic和atomic
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (nonatomic, strong) NSString *name;
@property (atomic, assign) int number;

@end
. 10000個異步任務,修改name屬性的值
- (void)nonatomic{
    for (NSInteger i = 0; i < 10000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.name = [NSString stringWithFormat:@"name:%ld", i];
        });
    }
}

執行結果:崩潰,崩潰原因是在子線程Thread8上,對象釋放了。

image.png

結果分析:
1、在MRC模式下,屬性name的set方法如下:

-(void)setName:(NSString *)name{
    if (_name != name) {
        [_name release];
        [name retain];
        _name = name;
    }
}

2、雖然在ARC模式下不用寫其set方法,但是在底層還是會走到這里
3、因為是多線程,且沒有加鎖保護,所以在一個線程走到[_name release]后,可能在另一個線程又一次去釋放,這時候造成崩潰。
4、把name屬性的nonatomic改成atomic就不會崩潰了,因為atomic加鎖了,是安全的。

. 接著上步說用atomic就安全了,再舉個例子

number屬性使用atomic修飾的

- (void)atomic{
    _number = 0;
    dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
        self->_number ++;
    });
    NSLog(@"_number:%d", _number);
}

執行結果:執行結果并不是10000,而且每次運行結果都不一樣,即運行結果不可預見。

image.png

結果分析:

_number++等價于
 int temp = _number+1;
 _number = temp;

雖然atomic保證了number屬性線程安全了,但是并不能保證temp變量的線程安全,又因為是多線程的,所以有可能同時執行多次 int temp = _number+1;才執行一次 _number = temp;導致結果越來越小,而且結果不可預知。

這時候就可以知道為什么不用atomic了:因為atomic會耗性能,而且大部分情況下并不會保證線程安全。

什么時候可以用atomic呢:在最簡單的,只有一個set時,簡單的讀寫實例變量。

UIKIT不需要使用atomic:因為UIKIT是在主線程做的,不存在線程安全問題。

4、NSLock

解決上步的問題

- (void)atomic{
    _number = 0;
    NSLock *lock = [[NSLock alloc] init];
    dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
        [lock lock];
        self->_number ++;
        [lock unlock];
    });
    NSLog(@"_number:%d", _number);
}

執行結果:

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。