IOS多线程编程简介
JefferyGott
8年前
<h2>IOS多线程编程简介</h2> <h2>基本概念</h2> <p>线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程是独立调度和分派的基本单位。同一进程中的多条线程将共享该进程中的全部系统资源,但是自有调用堆栈和寄存器环境。</p> <p>进程:进程是计算机中已运行程序的实体。其本身并不是几部运行单位,是线程的容器。</p> <p>任务:任务(task)用于指代抽象的概念,表示需要执行工作,具体可以是一个函数或者一个block。</p> <h2>IOS常用的多线程编程技术</h2> <p>IOS常用的多线程编程技术包括:NSThread、Cocoa NSOperation、GCD(grand central dispatch)。其优缺点对比如下表所示:</p> <table> <thead> <tr> <th>多线程技术</th> <th>优点</th> <th>缺点</th> </tr> </thead> <tbody> <tr> <td>NSThread</td> <td>轻量级最低,相对简单</td> <td>需要手动管理所有的线程活动(生命周期,休眠,同步等)线程同步对数据的加锁会有一定的系统开销</td> </tr> <tr> <td>Cocoa NSOperation</td> <td>自带线程周期管理,可只关注自己处理逻辑</td> <td>NSOperation是面向对象的抽象类,实现只能是其子类(NSInvocationOperation和NSBlockOperation),对象需要添加到NSOperationQueue队列里执行</td> </tr> <tr> <td>GCD</td> <td>效率高,可避免并发陷阱</td> <td>基于C而非OC实现</td> </tr> </tbody> </table> <p>如果对性能效率要求较高,处理大量并发时用GCD,简单而安全的可用NSOPeration或者NSThread。Apple推荐使用GCD</p> <h2>NSThread</h2> <p>NSThread具体的底层实现机制是Mach线程,实现技术有三种Cocoa threads、POSIX threads(UNIX)、Multiprocessing Services。</p> <h3>使用方式</h3> <ol> <li>显式创建方式: <pre> <code class="language-objectivec">-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument +(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument</code></pre> </li> <li>隐式创建方式: <pre> <code class="language-objectivec">[Object performSelectorInBackground:@selector(doSomething)withObject:nil];</code></pre> <h3>代码示例</h3> </li> </ol> <pre> <code class="language-objectivec">- (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> <h3>分析说明:</h3> <p>NSThread中线程内存管理,是否循环引用,同步问题都需要手动管理。除了代码中的lock还可以用@synchronized来简化NSLock的使用。</p> <pre> <code class="language-objectivec">- (void)doSomeThing:(id)anObj { @synchronized(anObj) { // Everything between the braces is protected by the @synchronized directive. } }</code></pre> <p>还可以用NSCondition中的wait消息设置等待线程,然后通过signal消息 发送信号的方式,在一个线程唤醒另外一个线程的等待。</p> <p>线程间通信:</p> <pre> <code class="language-objectivec">//在指定线程上执行操作 //在主线程上执行操作 [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; [self performSelector:@selector(run) onThread:threadName withObject:nil waitUntilDone:YES]; //在当前线程执行操作 [self performSelector:@selector(run) withObject:nil];</code></pre> <p>获取线程:</p> <pre> <code class="language-objectivec">NSThread *current = [NSThread currentThread]; NSThread *main = [NSThread mainThread];</code></pre> <h2>Cocoa NSOperation</h2> <h3>使用方式</h3> <p>NSOperation的使用方式有两种:</p> <ul> <li>使用NSInvocationOperation和NSBlockOperation</li> <li>自定义NSOperation子类。 <h3>代码示例</h3> </li> </ul> <pre> <code class="language-objectivec">-(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> <p>自定义NSOperation子类</p> <p>自定义NSOperation子类需要实现start、main、isExecuting、isFinish、isConcurrnet(asynchronous)方法。</p> <pre> <code class="language-objectivec">@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> <h3>分析说明</h3> <p>NSOperationQueue 是一个并行队列,可以通过设置maxConcurrentOperationCount来设定最大并行操作数,对于自定义实现的NSOperation子类,可以通过实现isConcurrent(isAsynchronous)方法来制定具体的操作是否同步执行。</p> <h2>GCD</h2> <h3>使用方式</h3> <p>GCD的工作原理是让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。</p> <p>队列的创建</p> <ul> <li>主线程队列(串行):<br> dispatch_get_main_queue()</li> <li>全局队列(并行):<br> dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)<br> 第一个参数说明队列的优先级,第二个参数暂未用到,默认0。根据优先级划分,共有四个全局队列 <pre> <code class="language-objectivec">#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</code></pre> </li> <li>自定义队列:<br> dispatch_queue_create(queuename, attr)<br> 第一个参数制定队列名称,自定义队列可以是串行也可以是并行的,由第二个参数attr决定。 <pre> <code class="language-objectivec">DISPATCH_QUEUE_SERIAL //(NULL) 串行 DISPATCH_QUEUE_SERIAL_INACTIVE DISPATCH_QUEUE_CONCURRENT //并行</code></pre> <strong>多线程执行</strong></li> <li>同步执行:<br> dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)</li> <li>异步执行:<br> dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)</li> <li>一次性执行:</li> </ul> <p>tatic dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ code to be executed once });</p> <ul> <li>延时执行:</li> </ul> <p>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 });</p> <ul> <li>循环迭代执行:<br> dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)<br> 能并发地执行不同的迭代。这个函数是同步的,所以和普通的 for 循环一样,它只会在所有工作都完成后才会返回。</li> <li>栅栏(barrier)执行:<br> dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)<br> 栅栏执行的意思是:在barrier前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。</li> <li>分组执行:<br> 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)<br> dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作<br> <strong>信号量</strong> <pre> <code class="language-objectivec">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)</code></pre> <strong>Dispatch Source</strong> <pre> <code class="language-objectivec">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> <h3>代码示例</h3> <strong>栅栏执行:</strong></li> </ul> <pre> <code class="language-objectivec">- (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> <p>输出:</p> <pre> <code class="language-objectivec">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</code></pre> <p>分组执行:</p> <pre> <code class="language-objectivec">- (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> <p>输出:</p> <pre> <code class="language-objectivec">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</code></pre> <p>信号量:</p> <pre> <code class="language-objectivec">- (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> <p>输出:</p> <pre> <code class="language-objectivec">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> <p>Dispatch Source</p> <pre> <code class="language-objectivec">- (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); }</code></pre> <p>同步异步串行并行简单常用未列出</p> <h3>分析说明</h3> <p>同步异步和并行串行</p> <table> <thead> <tr> <th> </th> <th>并行队列</th> <th>串行队列</th> <th>主队列</th> </tr> </thead> <tbody> <tr> <td><strong>异步执行</strong></td> <td>开启多个新的线程,任务同时执行</td> <td>开启一个新的线程,任务按顺序执行</td> <td>不开启新的线程,任务按顺序执行</td> </tr> <tr> <td><strong>同步执行</strong></td> <td>不开启新的线程,任务按顺序执行</td> <td>不开启新的线程,任务按顺序执行</td> <td>死锁</td> </tr> </tbody> </table> <p>对于GCD中的block执行,都有对应的function执行函数,比如:</p> <pre> <code class="language-objectivec">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)</code></pre> <p>信号量</p> <p>dispatch_semaphore_create(N);N>=0时可以正常执行,当N小于0时一直等待</p> <p>代码示例的解释:创建了一个初使值为5的semaphore,每一次for循环都会创建一个新的线程(具体看GCD的线程池,此处认为是新建),线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了5个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为5的一个线程队列。</p> <p>Dispatch Source</p> <p>它基本上就是一个低级函数的 grab-bag,可监听事件如下:</p> <p>DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。</p> <p>DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。</p> <p>DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。</p> <p>DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。</p> <p>DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。</p> <p>DISPATCH_SOURCE_TYPE_READ:读文件事件。</p> <p>DISPATCH_SOURCE_TYPE_WRITE:写文件事件。</p> <p>DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。</p> <p>DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。</p> <p>DISPATCH_SOURCE_TYPE_TIMER:定时器事件。</p> <p>DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。</p> <p> </p> <p>来自:http://www.jianshu.com/p/af0a221896ba</p> <p> </p>