IOS多線程編程簡介
IOS多線程編程簡介
基本概念
線程:線程是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。線程是獨立調度和分派的基本單位。同一進程中的多條線程將共享該進程中的全部系統資源,但是自有調用堆棧和寄存器環境。
進程:進程是計算機中已運行程序的實體。其本身并不是幾部運行單位,是線程的容器。
任務:任務(task)用于指代抽象的概念,表示需要執行工作,具體可以是一個函數或者一個block。
IOS常用的多線程編程技術
IOS常用的多線程編程技術包括:NSThread、Cocoa NSOperation、GCD(grand central dispatch)。其優缺點對比如下表所示:
多線程技術 | 優點 | 缺點 |
---|---|---|
NSThread | 輕量級最低,相對簡單 | 需要手動管理所有的線程活動(生命周期,休眠,同步等)線程同步對數據的加鎖會有一定的系統開銷 |
Cocoa NSOperation | 自帶線程周期管理,可只關注自己處理邏輯 | NSOperation是面向對象的抽象類,實現只能是其子類(NSInvocationOperation和NSBlockOperation),對象需要添加到NSOperationQueue隊列里執行 |
GCD | 效率高,可避免并發陷阱 | 基于C而非OC實現 |
如果對性能效率要求較高,處理大量并發時用GCD,簡單而安全的可用NSOPeration或者NSThread。Apple推薦使用GCD
NSThread
NSThread具體的底層實現機制是Mach線程,實現技術有三種Cocoa threads、POSIX threads(UNIX)、Multiprocessing Services。
使用方式
- 顯式創建方式:
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument +(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
- 隱式創建方式:
[Object performSelectorInBackground:@selector(doSomething)withObject:nil];
代碼示例
- (void)testNSThread{
_lock = [[NSLock alloc] init];
_condition = [[NSCondition alloc] init];
self.total = 20;
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread1 setName:@"Thread--1"];
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread2 setName:@"Thread--2"];
[thread2 start];
// [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// [self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run{
while (YES) {
[self.lock lock];
if(self.total >= 0){
[NSThread sleepForTimeInterval:0.09];
NSInteger count = 20 - self.total;
NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
self.total--;
}else{
break;
}
[self.lock unlock];
}
}</code></pre>
分析說明:
NSThread中線程內存管理,是否循環引用,同步問題都需要手動管理。除了代碼中的lock還可以用@synchronized來簡化NSLock的使用。
- (void)doSomeThing:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}</code></pre>
還可以用NSCondition中的wait消息設置等待線程,然后通過signal消息 發送信號的方式,在一個線程喚醒另外一個線程的等待。
線程間通信:
//在指定線程上執行操作
//在主線程上執行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
[self performSelector:@selector(run) onThread:threadName withObject:nil waitUntilDone:YES];
//在當前線程執行操作
[self performSelector:@selector(run) withObject:nil];
獲取線程:
NSThread *current = [NSThread currentThread];
NSThread *main = [NSThread mainThread];
Cocoa NSOperation
使用方式
NSOperation的使用方式有兩種:
- 使用NSInvocationOperation和NSBlockOperation
- 自定義NSOperation子類。
代碼示例
-(void)testNSOPeration{
self.total = 20;
NSInvocationOperation operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
NSOperationQueue queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation1];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self run2];
}];
// [operation1 start];//當前線程直接執行
[operation2 addDependency:operation1];
// [operation2 addExecutionBlock:^{
// [self run];
// }];
[queue addOperation:operation2];
// queue.maxConcurrentOperationCount = 1;
}
- (void)run2{
while (YES) {
if(self.total >= 0){
// [NSThread sleepForTimeInterval:0.09];
NSInteger count = 20 - self.total;
NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
self.total--;
}else{
break;
}
}
}</code></pre>
自定義NSOperation子類
自定義NSOperation子類需要實現start、main、isExecuting、isFinish、isConcurrnet(asynchronous)方法。
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
(id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
(BOOL)isConcurrent {
return YES;
}
(BOOL)isExecuting {
return executing;
}
(BOOL)isFinished {
return finished;
}
@end</code></pre>
分析說明
NSOperationQueue 是一個并行隊列,可以通過設置maxConcurrentOperationCount來設定最大并行操作數,對于自定義實現的NSOperation子類,可以通過實現isConcurrent(isAsynchronous)方法來制定具體的操作是否同步執行。
GCD
使用方式
GCD的工作原理是讓程序平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。
隊列的創建
- 主線程隊列(串行):
dispatch_get_main_queue()
- 全局隊列(并行):
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
第一個參數說明隊列的優先級,第二個參數暫未用到,默認0。根據優先級劃分,共有四個全局隊列
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
- 自定義隊列:
dispatch_queue_create(queuename, attr)
第一個參數制定隊列名稱,自定義隊列可以是串行也可以是并行的,由第二個參數attr決定。
DISPATCH_QUEUE_SERIAL //(NULL) 串行
DISPATCH_QUEUE_SERIAL_INACTIVE
DISPATCH_QUEUE_CONCURRENT //并行
多線程執行
- 同步執行:
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
- 異步執行:
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
- 一次性執行:
tatic dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ code to be executed once });
- 延時執行:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ code to be executed after a specified delay });
- 循環迭代執行:
dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。
- 柵欄(barrier)執行:
dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
柵欄執行的意思是:在barrier前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行。
- 分組執行:
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作
信號量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(semaphore);
dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds); dispatch_semaphore_wait(semaphore, timeoutTime)
Dispatch Source
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatchQueue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, intervalInSeconds NSEC_PER_SEC, leewayInSeconds NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
code to be executed when timer fires
});
dispatch_resume(timer);</code></pre>
代碼示例
柵欄執行:</li>
</ul>
- (void)testBarrier{
dispatch_queue_t queue = dispatch_queue_create("com.zyw.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:2];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
}</code></pre>
輸出:
2016-12-02 17:17:24.710 TestThread[1434:164682] dispatch_async1
2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_async2
2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_barrier_async
2016-12-02 17:17:28.720 TestThread[1434:164681] dispatch_async3
分組執行:
- (void)testGroup{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"EndAllgroupTask");
});
// 第二種使用方法
// dispatch_group_enter(group);
// dispatch_group_leave(group);
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//
// });
}</code></pre>
輸出:
2016-12-02 17:11:50.592 TestThread[1407:161853] group1
2016-12-02 17:11:51.591 TestThread[1407:161852] group2
2016-12-02 17:11:52.589 TestThread[1407:161855] group3
2016-12-02 17:11:52.590 TestThread[1407:161635] EndAllgroupTask
信號量:
- (void)testSemaphore{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (NSInteger i = 0 ; i < 10; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"*****%zd", i);
[NSThread sleepForTimeInterval:1];
dispatch_semaphore_signal(semaphore);
});
}
}</code></pre>
輸出:
2016-12-02 17:36:11.071 TestThread[1496:173204] *1
2016-12-02 17:36:11.071 TestThread[1496:173207] *2
2016-12-02 17:36:11.071 TestThread[1496:173203] *0
2016-12-02 17:36:11.071 TestThread[1496:173242] *3
2016-12-02 17:36:11.071 TestThread[1496:173243] *4
2016-12-02 17:36:12.074 TestThread[1496:173244] *5
2016-12-02 17:36:12.074 TestThread[1496:173250] *9
2016-12-02 17:36:12.074 TestThread[1496:173246] *7
2016-12-02 17:36:12.074 TestThread[1496:173245] *6
2016-12-02 17:36:12.074 TestThread[1496:173247] *8</code></pre>
Dispatch Source
- (void)testSource{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 5);
dispatch_source_set_timer(timer, start, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"******Hello");
});
//啟動
dispatch_resume(timer);
}
同步異步串行并行簡單常用未列出
分析說明
同步異步和并行串行
并行隊列
串行隊列
主隊列
異步執行
開啟多個新的線程,任務同時執行
開啟一個新的線程,任務按順序執行
不開啟新的線程,任務按順序執行
同步執行
不開啟新的線程,任務按順序執行
不開啟新的線程,任務按順序執行
死鎖
對于GCD中的block執行,都有對應的function執行函數,比如:
dispatch_sync_f(dispatch_queue_t _Nonnull queue, void * _Nullable context, dispatch_function_t _Nonnull work)
dispatch_async_f(dispatch_queue_t _Nonnull queue, void * _Nullable context, dispatch_function_t _Nonnull work)
信號量
dispatch_semaphore_create(N);N>=0時可以正常執行,當N小于0時一直等待
代碼示例的解釋:創建了一個初使值為5的semaphore,每一次for循環都會創建一個新的線程(具體看GCD的線程池,此處認為是新建),線程結束的時候會發送一個信號,線程創建之前會信號等待,所以當同時創建了5個線程之后,for循環就會阻塞,等待有線程結束之后會增加一個信號才繼續執行,如此就形成了對并發的控制,如上就是一個并發數為5的一個線程隊列。
Dispatch Source
它基本上就是一個低級函數的 grab-bag,可監聽事件如下:
DISPATCH_SOURCE_TYPE_DATA_ADD:屬于自定義事件,可以通過dispatch_source_get_data函數獲取事件變量數據,在我們自定義的方法中可以調用dispatch_source_merge_data函數向Dispatch Source設置數據,下文中會有詳細的演示。
DISPATCH_SOURCE_TYPE_DATA_OR:屬于自定義事件,用法同上面的類型一樣。
DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口發送事件。
DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
DISPATCH_SOURCE_TYPE_PROC:與進程相關的事件。
DISPATCH_SOURCE_TYPE_READ:讀文件事件。
DISPATCH_SOURCE_TYPE_WRITE:寫文件事件。
DISPATCH_SOURCE_TYPE_VNODE:文件屬性更改事件。
DISPATCH_SOURCE_TYPE_SIGNAL:接收信號事件。
DISPATCH_SOURCE_TYPE_TIMER:定時器事件。
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:內存壓力事件。
來自:http://www.jianshu.com/p/af0a221896ba