多线程Note

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多线程,你看我就够了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容