注意幾個名詞:
同步:不會開啟子線程, 而且會阻塞當前線程
異步:不會阻塞當前線程, 且具備開啟線程的能力(不一定開啟)
任務:block里面的代碼塊
串行隊列:只會開啟一個子線程
并行隊列:可以開啟多個子線程,但是不一定一定開啟線程,因為開啟一個線程會:消耗CPU資源、占內存(子線程512kb,主線程1M)能開啟幾個線程取決于內核,一般開啟3-6個就可以了
主隊列:異步添加任務必須等到主線程中的任務執行完才執行,就是一個串行隊列,但是在主隊列中添加的異步任務不會開啟子線程
第一部分:基礎
一、同步串行
//同步串行
/*
同步: 看到這兩個字首先想到的是不會開啟子線程,而且會阻塞當前線程
*/
- (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");
}
執行結果:
二、異步串行
//異步串行
/*
異步: 要想到的是不會阻塞當前線程,且具備開啟線程的能力(不一定會創建子線程,例如在串行隊列中,只會創建一條子線程)
*/
- (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");
}
執行結果:
三、異步并行
//異步并行
- (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");
}
執行結果:
四、同步并行
//同步并行
- (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");
}
執行結果:
第二部分:嵌套
一、同步并行嵌套異步任務
//同步并行嵌套異步任務
- (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");
}
執行結果:
結果分析:
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");
}
執行結果:
結果分析:
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");
}
執行結果:死鎖
結果分析:
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");
}
執行結果:
結果分析:
1、主隊列中異步添加任務必須等到主線程中的任務執行完才執行
2、主隊列就是一個串行隊列,但是在主隊列中添加的異步任務不會開啟子線程
五、主隊列中添加同步任務
//主隊列中添加同步任務
- (void)main1{
//任務1
NSLog(@"1111");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
//block任務2
NSLog(@"2222");
});
NSLog(@"3333");
}
執行結果:死鎖
結果分析:
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");
}
執行結果:
結果分析:
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");
});
}
執行結果
結果分析:
在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");
}
執行結果:
結果分析:
可以看出先執行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");
}
執行結果:
結果分析:
可以看出,同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");
}
執行結果:
第四部分:線程鎖(線程安全)
線程不安全:內存數據被多個線程讀取之后,出現的結果不可預見。
線程鎖:確保在讀取數據時只有一條線程再操作。
這里總結三種鎖: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");
});
}
執行順序:
出現線程不安全,因此需要加鎖。
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");
}
執行結果:
結果分析:
等到一個線程完成,才會執行另一個線程。
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");
}
執行結果:
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上,對象釋放了。
結果分析:
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,而且每次運行結果都不一樣,即運行結果不可預見。
結果分析:
_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);
}
執行結果: