美文网首页
多线程Note

多线程Note

作者: MadLife程序员 | 来源:发表于2015-12-10 10:14 被阅读26次

    iOS里多线程有四种解决方案

    1.Pthreads
    2.NSThread
    3.GCD
    4.NSOperation & NSOperationQueue

    1.Pthreads

    这是一套通用的的解决方案,因为这是符合POSIX标准的线程,基于C语言,在类Unix操作系统(Unix...Linux...Mac OS X)都使用了Pthreads作为操作系统的线程。移植性强,通用。

    2.NSThread

    这是经过苹果公司封装后,具备面向对象思想的多线程解决方案。可以直接操控线程对象,但是生命周期仍然需要我们自己手动管理,在开发过程中我们会经常使用NSTread.currentThread()来获取当前的线程对象,调试方便。

    Swift
        let thread = NSThread(target: self, selector: "threadTask", object: nil)        
        thread.start()
    
        func threadTask() {
            NSLog(NSThread.currentThread())
        }
    

    在OC里performSelector:这个方法因为被苹果认为不安全,因此在Swift里去掉了。

    原因是在假如Selector是在运行时动态选择的话,编译器并不了解调用的方法是否有返回值,因此并能不能运用ARC的内存规则来判断返回值是否应该释放,ARC采用谨慎的做法-不添加释放操作,这样可能会导致内存泄漏。
    

    然而这个解决方案并不够智能,接下来的GCDNSOperation & NSOperationQueue是我们在项目里用得比较多的。

    3.GCD

    GCD-Grand Central Dispatch.是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包)。

    GCD中有两个重要的概念:任务队列

    任务:

    即你想要添加的操作,在GCD中用Block表示。任务有两种执行的方式:同步执行异步执行,区别在于会不会阻塞当前线程直到Block任务执行完成

    同步执行:会阻塞当前进程并等待Block中的任务执行完成,然后当前线线程彩绘继续往下运行。

    异步执行:当前线程继续往下执行,不会阻塞当前线程。

    队列:

    串行队列:根据队列中定义的fifo执行,先进先出的顺序。

    并行队列:放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

    然后组合起来就是:
                同步执行                异步执行
    串行队列    当前线程,一个一个执行 其他线程,一个一个执行
    并行队列    当前线程,一个一个执行 开很多线程,一起执行
    
    创建队列:

    主队列:特殊的串行队列,负责所有UI的刷新工作。

    Swift
        let queue = ispatch_get_main_queue()    
    
    自己创建的队列
    Swift
        let queue = dispatch_queue_create("whatNameYouLike", nil);
        let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_SERIAL)
        //并行队列
        let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
    
    全局并行队列

    这是系统提供的一个并发队列。

    Swift
      let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    
    创建任务:

    同步任务:阻塞当前线程(SYNC)

    Swift
        let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
        dispatch_sync(queue) { () -> Void in
            NSLog(NSThread.currentThread())
        }
    

    异步任务:不会阻塞当前线程(ASYNC)

    Swift
        let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
        dispatch_async(queue) { () -> Void in
            NSLog(NSThread.currentThread())
        }
    

    队列组: 是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完成,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。其中最值得注意的用法,就是把将要并罚执行的多个任务合为一组,于是调用者就可以知道任务何时全部完成。

    Swift
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
           let group = dispatch_group_create()
           
        for index in 1...3 {
            dispatch_group_async(group, queue, { () -> Void in
                for i in 1...3 {
                    NSLog("group-\(index)-\(i) - \(NSThread.currentThread())")
                }
            })
        }
        
        // 需要等待
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
        
        // 假如不需要等待,完成后自动通知
        dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
            NSLog("完成 - \(NSThread.currentThread())")
        }
    

    栅栏:

    在队列中,栅栏块必须单独执行,不能与其他块并行。这仅对并行队列是有意义的,因为串行队列总是按顺序逐个执行。并行队列如果发现接下来要处理的块是栅栏块,就会一直等待前面的并发块都执行完毕,才会单独执行这个栅栏块。

    Swift
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        
        var some:String = ""
        
        func getSome() {
            dispatch_sync(queue) { () -> Void in
                self.some = _some
            }
        }
        
        func setSome() {
            dispatch_barrier_async(queue) { () -> Void in
                _some = self.some
            }
        }   
    

    4.NSOperation & NSOperationQueue

    NSOperation 是苹果公司对 GCD 的封装,Objective-C的对象,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperationNSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:

    1.将要执行的任务封装到一个 NSOperation 对象中。
    2.将此任务添加到一个 NSOperationQueue 对象中。

    添加任务:

    NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperationNSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

    NSInvocationOperation : 需要传入一个方法名。

    值得一提的是,这个方法在Swift里被认为是不安全的,理由同performSelector:方法。

    NSBlockOperation : 需要传入一个Block。
    Swift
        let opr = NSBlockOperation { () -> Void in
                NSLog("Inside block operation -- \(NSThread.currentThread())")
            }
            
        opr.start() 
    

    NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。

    Swift
        let opr = NSBlockOperation { () -> Void in
                    NSLog("Inside block operation -- \(NSThread.currentThread())")
                }
            
        for i in 1...5 {
            opr.addExecutionBlock({ () -> Void in
                NSLog("\(i) - \(NSThread.currentThread())")
            })
        }
        
        opr.start()
    

    自定义Operation:

    除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。

    创建队列:

    我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列只要添加到队列,会自动调用任务的 start() 方法

    主队列:

    每套多线程方案都会有一个主线程(当然啦,说的是iOS中,像 pthread 这种多系统的方案并没有,因为 UI线程 理论需要每种操作系统自己定制)。这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。

    Swift
        NSLog("\(NSOperationQueue.mainQueue())")
    
    其他队列:

    因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列会在其他线程异步执行。因为只有这两种队列,除了主队列,其他队列就不需要名字了。

    Swift
        let queue = NSOperationQueue()
        
        let opr = NSBlockOperation { () -> Void in
            NSLog("---- \(NSThread.currentThread())")
        }
        
        // 添加其他block
        for i in 1...3 {
            opr.addExecutionBlock { () -> Void in
                NSLog("----\(i)---- \(NSThread.currentThread())")
            }
        }
        
        queue.addOperation(opr)
    

    NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?

    这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,相当于是串行。

    NSOperationQueue 还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block; ,这是就和GCD类似了。这样就可以添加一个任务到队列中了,十分方便。

    NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

    Swift
        //1.任务一:下载图片
         let operation1 = NSBlockOperation { () -> Void in
             NSLog("下载图片 - %@", NSThread.currentThread())
             NSThread.sleepForTimeInterval(1.0)
         }
         
         //2.任务二:打水印
         let operation2 = NSBlockOperation { () -> Void in
             NSLog("打水印   - %@", NSThread.currentThread())
             NSThread.sleepForTimeInterval(1.0)
         }
         
         //3.任务三:上传图片
         let operation3 = NSBlockOperation { () -> Void in
             NSLog("上传图片 - %@", NSThread.currentThread())
             NSThread.sleepForTimeInterval(1.0)
         }
         
         //4.设置依赖
         operation2.addDependency(operation1)    //任务二依赖任务一
         operation3.addDependency(operation2)    //任务三依赖任务二
         
         //5.创建队列并加入任务
         let queue = NSOperationQueue()
         queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)
         
    

    如何返回主线程

    • GCD里我们知道
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
    
    })
    

    就能返回主线程执行UI操作.

    • NSOperationQueue里我们是这样的:
    NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
                
    }
    

    就能返回主线程执行UI操作.

    其他的一些属性

    • NSOperation

    BOOL executing; //判断任务是否正在执行

    BOOL finished; //判断任务是否完成

    void (^completionBlock)(void); //用来设置完成后需要执行的操作

    (void)cancel; //取消任务

    (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

    • NSOperationQueue

    NSUInteger operationCount; //获取队列的任务数

    (void)cancelAllOperations; //取消队列中所有的任务

    (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕

    [queue setSuspended:YES]; // 暂停queue

    [queue setSuspended:NO]; // 继续queue

    参考资料:

    Effective Objective-C 2.0

    关于iOS多线程,你看我就够了

    相关文章

      网友评论

          本文标题:多线程Note

          本文链接:https://www.haomeiwen.com/subject/mccyhttx.html