iOS多线程总结

多线程 优缺点,实际应用


多线程比较

死锁:向同一个/当前的串行队列添加同步sync操作任务,会产生死锁,新等旧,旧等新;
【死锁的四个必要条件】或【死锁的四个特性】:
1.互斥条件:一个资源每次只能被一个进程使用。如果一个进程已经占用了某个资源,那么其他进程就必须等待,直到该资源被释放。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只有这四个条件同时成立,才会发生死锁。

主线程:程序启动后默认开启的线程,也是唯一可以进行UI更新的线程。所有的UIKit类都应在主线程上访问。
主队列:系统提供的一个特殊的串行队列,放在主队列中的任务都会在主线程中执行。

1- NSThread:
–优点:NSThread 比其他两个轻量级,使用简单
–缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

2- NSOperation:
–不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
–NSOperation是面向对象的
核心概念:把操作(异步)添加到队列(全局的并发队列)
OC 框架,更加面向对象,是对 GCD 的封装
iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层全部重写
Operation作为一个对象,为我们提供了更多的选择
可以随时取消已经设定要准备执行的任务,已经执行的除外
可以跨队列设置操作的依赖关系
可以设置队列中每一个操作的优先级
高级功能:
最大操作并发数(GCD不好做)
继续/暂停/全部取消
跨队列设置操作的依赖关系

3- GCD:
–Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和强大的技术
–GCD是基于C语言的
将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
GCD是底层的C语言构成的API
iOS 4.0 推出的,针对多核处理器的并发技术
在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
要停止已经加入 queue 的 block 需要写复杂的代码
需要通过 Barrier 或者同步任务设置任务之间的依赖关系
只能设置队列的优先级
高级功能:
一次性 once
延迟操作 after
调度组

比较
NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
GCD主要与block结合使用。代码简洁高效

  1. 性能:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话
  2. 从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
  3. 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:

  1. Serial:又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
  2. Concurrent:又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
  3. Main dispatch queue:它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

dispatch_sync(),同步添加操作。等待添加进队列里面的操作完成之后再继续执行。调用以后等到block执行完以后才返回 ,dispatch_sync()会阻塞当前线程。
dispatch_async ,异步添加进任务队列,调用以后立即返回,它不会做任何等待

在多线程开发当中,程序员只要将想做的事情定义好,并追加到DispatchQueue(派发队列)当中就好了。派发队列分为两种,一种是串行队列(SerialDispatchQueue),一种是并行队列(ConcurrentDispatchQueue)。一个任务就是一个block,比如,将任务添加到队列中的代码是:dispatch_async(queue, block);当给queue添加多个任务时:

  1. queue是串行队列,则它们按顺序一个个执行,同时处理的任务只有一个。
  2. queue是并行队列时,不论第一个任务是否结束,都会立刻开始执行后面的任务,也就是可以同时执行多个任务。但是并行执行的任务数量取决于XNU内核,是不可控的。比如,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,由系统控制。
  1. GCD组
- (void) gcd_group {
        
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    queue = dispatch_get_global_queue(0, 0);
    
    dispatch_queue_t queue_1 = dispatch_queue_create("queue_1", DISPATCH_QUEUE_CONCURRENT);
    queue_1 = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    queue_1 = dispatch_get_global_queue(0, 0);
    queue_1 = queue;
    
    // 方式一
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 1 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 2 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 3 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 4 - %@", [NSThread currentThread]);
    });
    
    
    // 方式二
    // 在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
    // dispatch_group_enter|dispatch_group_leave必须要配对使用
    // 测试 当queue与queue_1 不一致是,组完成会提前完成
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 1 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 2 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 3 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 4 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    // 第一种拦截方法
    dispatch_group_notify(group, queue_1, ^{
        NSLog(@"组完成 - %@", [NSThread currentThread]);
    });

    //    dispatch_group_notify_f(group, queue, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>)
    
    /**
     第二种拦截方法
     参数
     1./队列组
     2./时间
     现在 DISPATCH_TIME_NOW    : 传现在不会起到拦截作用,会马上执行
     未来 DISPATCH_TIME_FOREVER: 等到队列组中的所有任务都执行完成后才会触发.也能起到监听队列组的效果
     这个方法时【阻塞】的,队列组内任务不执行完成,下面的代码永远不会执行.
     */
    // DISPATCH_TIME_NOW  DISPATCH_TIME_FOREVER
    dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"--验证--");
    
    
    // 执行task
    dispatch_async_f(queue, (__bridge void * _Nullable)(@{@"1":@"2"}), task);
}

void task(void*param){
    
    NSLog(@"%s - %@, param - %@",__func__,[NSThread currentThread], param);
}
  1. GCD栅栏障碍-任务分割 dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。苹果文档中指出,如果使用的是全局队列或者创建的不是并发队列,则dispatch_barrier_async实际上就相当于dispatch_async。
/**
 * 栅栏方法 dispatch_barrier_sync / dispatch_barrier_async
 */
- (void)gcd_barrier {
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);//【注意】
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    // dispatch_barrier_sync
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"barrier---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"4---%@",[NSThread currentThread]);
        }
    });
}

6.1. 关于定义队列:
在不通位置,虽然定义队列的名称和串并行类型都相同,但队列不同;
即:如果要使用同一个队列,队列必须被持有,然后使用该队列。
如例子中的队列打印:
(lldb) po queue2
<OS_dispatch_queue_concurrent: com.test.queue[0x2833d9400不同] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos[0x105ae52c0], width = 0xffe, state = 0x0000041000000000, in-flight = 0}>
(lldb) po queue1
<OS_dispatch_queue_concurrent: com.test.queue[0x2833d9500不同] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos[0x105ae52c0], width = 0xffe, state = 0x0000041000000000, in-flight = 0}>
(lldb) po queue3
<OS_dispatch_queue_concurrent: com.test.queue[0x2833c5180不同] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos[0x105ae52c0], width = 0xffe, state = 0x0000041000000000, in-flight = 0}>

- (void) fun_test1 {

    dispatch_queue_t queue2 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

    [self fd1];
    dispatch_barrier_async(queue2, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 2");
    });
    [self fd3];
}

- (void) fd1 {

    dispatch_queue_t queue1 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue1, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"Log==> Test==> crash 11");
    });
}

- (void) fd3 {

    dispatch_queue_t queue3 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue3, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 33");
    });
}

/** 
预想打印结果:
 Log==> Test==> crash 11
 Log==> Test==> crash 2
 Log==> Test==> crash 33

实际打印结果:
 Log==> Test==> crash 33
 Log==> Test==> crash 2
 Log==> Test==> crash 11
*/

- (void) fun_test2 {
    
    dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue, ^{

        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"Log==> Test==> crash 1");
    });

    dispatch_barrier_async(queue, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 2");
    });

    dispatch_barrier_async(queue, ^{

        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 3");
    });
}

/** 
预想打印结果:
 Log==> Test==> crash 1
 Log==> Test==> crash 2
 Log==> Test==> crash 3

实际打印结果:
 Log==> Test==> crash 1
 Log==> Test==> crash 2
 Log==> Test==> crash 3

*/

7.各种锁
@synchronized:适用线程不多,任务量不大的多线程加锁
NSLock:线程锁,其实NSLock并没有想象中的那么差,不知道大家为什么不推荐使用
dispatch_semaphore_t:使用信号来做加锁,性能提升显著
NSCondition:断言,使用其做多线程之间的通信调用不是线程安全的
NSConditionLock:条件锁,单纯加锁性能非常低,比NSLock低很多,但是可以用来做多线程处理不同任务的通信调用
NSRecursiveLock:递归锁/循环锁,性能出奇的高,但是只能作为递归使用,所以限制了使用场景
NSDistributedLock:分布式锁,MAC系统使用

POSIX(pthread_mutex):底层的api,复杂的多线程处理建议使用,并且可以封装自己的多线程
OSSpinLock:自旋锁,性能也非常高,可惜出现了线程问题,适合较短时间等待case
dispatch_barrier_async/dispatch_barrier_sync:dispatch_barrier_sync比dispatch_barrier_async性能要高,真是大出意外

多线程性能比较

Dispatch Semaphore 提供了三个函数:

  1. dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
  2. dispatch_semaphore_signal:发送一个信号,让信号总量加1
  3. dispatch_semaphore_wait:当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行;可以使总信号量减1。
信号量的使用前提:确定需要处理哪个线程等待(阻塞),需要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore 在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁

如:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

- (void)gcd_semaphoreSync {
    
    NSLog(@"currentThread - %@",[NSThread currentThread]);
    NSLog(@"semaphore - begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int var = 0;
    dispatch_async(queue, ^{
        // 任务1
        [NSThread sleepForTimeInterval:1];
        NSLog(@"1 currentThread - %@",[NSThread currentThread]);
        var = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore - end,var = %d",var);
}
- (void) gcd_threadSafety {
    
    NSLog(@"currentThread - %@",[NSThread currentThread]);

    __block NSInteger ticketSurplusCount = 10;
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
     
    dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);//
    NSLock *myLock = [NSLock new];
    NSCondition *myCondition = [NSCondition new];
    
    void(^saleTicketSafe)(void) = ^ {
        
        while (1) {
            
            [myLock lock];
            [myCondition lock];
            @synchronized (self)
            {                    
                 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);// 相当于加锁                
                if (ticketSurplusCount > 0) {
                    
                    ticketSurplusCount--;
                    NSLog(@"%@", [NSString stringWithFormat:@"余票为:%ld 窗口:%@", (long)ticketSurplusCount, [NSThread currentThread]]);
                    [NSThread sleepForTimeInterval:0.2];
                    dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                }
                else {
                    
                    NSLog(@"已售完");
                     dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                    break;
                }
            }
            [myLock unlock];
            [myCondition unlock];
        }
    };
    
    dispatch_async(queue1, ^{
        saleTicketSafe();
    });
    
    dispatch_async(queue2, ^{
        saleTicketSafe();
    });
}

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