美文网首页
GCD部分总结

GCD部分总结

作者: yahibo | 来源:发表于2019-07-05 19:17 被阅读0次

一、概述

多线程任务管理,基于C语言的底层API,采用闭包形式与外界通讯,代码简洁高效;充分利用多核CPU,自动管理线程的生命周期,我们只负责任务的创建。

二、队列和任务

1、队列

常用的数据结构之一,具有先进先出(FIFO)的特性,是一种受限制的线性表,只允许在表头删除任务,在表尾部插入。队列的这种特点生活中很常见,例如学校餐厅排队就餐,银行取号排队办理业务等,先到先得,后来的要在队尾排队。

1.1、在GCD中队列分为串行队列和并行队列。

串行队列:在同一个队列中,一个任务结束后才能创建新的线程并执行任务。
并行队列:在同一个队列中,同时创建多个线程,并行执行任务。
例如行车路上,道路狭窄,车辆需要前后排列依次通行,这时我们叫串行队列,在100米宽的道路上,车辆可以并排行进这时我们叫并行队列。

1.2、创建队列

创建队列使用dispatch_queue_create方法。

dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
label:队列标签,队列编号
Attr:标识队列类型,串行还是并行

创建串行队列:

 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

创建并行队列:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

1.3、获取队列

除创建队列外,还可以获取系统中默认存在的两个队列:主队列和全局队列。
主队列:串行队列,主队列中的任务都会放在主线程中执行。获取主线程队列:

dispatch_queue_t queue =dispatch_get_main_queue()

全局队列:又称全局并行队列。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

第一个参数:队列优先级,范围如下:

DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

一般使用DISPATCH_QUEUE_PRIORITY_DEFAULT

第二个参数:标识位置0即可

2、任务

任务有同步任务与异步任务,同步任务直接加入到主线程中执行,异步任务会创建新的线程。

GCD提供了同步任务创建方法和异步任务创建方法。

同步任务:

dispatch_sync(queue, ^{
    //同步任务执行代码
});

异步任务:

dispatch_async(queue, ^{
    //异步任务执行代码
});

三、队列任务的使用

任何任务都要放在队列中执行,以上有两种类型的队列,有两种类型的任务,因此队列与任务有四种不同组合:

并行队列+同步任务
并行队列+异步任务
串行队列+同步任务
串行队列+异步任务

除开发人员创建的队列外还存在串行主队列与并行全局队列,而全局并行队列可以作为普通并行队列使用,因此又产生了两种任务队列组合:

主队列+同步任务
主队列+异步任务

1、并行队列+同步任务

并行队列允许同时创建多个线程,同步任务不会创建新的线程,直接加入到主线程中。
验证代码如下:

/**********并行队列+同步任务**********/
-(void)concurrent_sync{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue1->i:%d",i);//打印任务
        }
    });
    dispatch_sync(queue, ^{
        NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue2->i:%d",i);//打印任务
        }
    });
    dispatch_sync(queue, ^{
        NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue3->i:%d",i);//打印任务
        }
    });
}

执行结果:

2019-07-05 15:23:33.581267+0800 GCD2[26698:412634] queue1:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:33.685574+0800 GCD2[26698:412634] queue1->i:0
2019-07-05 15:23:33.790259+0800 GCD2[26698:412634] queue1->i:1
2019-07-05 15:23:33.892951+0800 GCD2[26698:412634] queue1->i:2
2019-07-05 15:23:33.893122+0800 GCD2[26698:412634] queue2:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:33.996298+0800 GCD2[26698:412634] queue2->i:0
2019-07-05 15:23:34.098246+0800 GCD2[26698:412634] queue2->i:1
2019-07-05 15:23:34.201317+0800 GCD2[26698:412634] queue2->i:2
2019-07-05 15:23:34.201488+0800 GCD2[26698:412634] queue3:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:34.305725+0800 GCD2[26698:412634] queue3->i:0
2019-07-05 15:23:34.407814+0800 GCD2[26698:412634] queue3->i:1
2019-07-05 15:23:34.511323+0800 GCD2[26698:412634] queue3->i:2

总结:打印任务都是在主线程中执行,没有开启新线程,任务顺序执行。同步任务不创建新线程尽管并行队列允许。

2、并行队列+异步任务

允许创建多个线程,同时执行任务。

/**********并行队列+异步任务**********/
-(void)concurrent_async{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue1->i:%d",i);//打印任务
        }
    });
    dispatch_async(queue, ^{
        NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue2->i:%d",i);//打印任务
        }
    });
    dispatch_async(queue, ^{
        NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue3->i:%d",i);//打印任务
        }
    });
}

执行结果:

2019-07-05 15:44:40.541135+0800 GCD2[26918:423494] queue1:<NSThread: 0x600002ca6d40>{number = 4, name = (null)}
2019-07-05 15:44:40.541153+0800 GCD2[26918:423496] queue2:<NSThread: 0x600002c99680>{number = 3, name = (null)}
2019-07-05 15:44:40.541167+0800 GCD2[26918:423504] queue3:<NSThread: 0x600002c88e80>{number = 5, name = (null)}
2019-07-05 15:44:40.643411+0800 GCD2[26918:423504] queue3->i:0
2019-07-05 15:44:40.643411+0800 GCD2[26918:423494] queue1->i:0
2019-07-05 15:44:40.643411+0800 GCD2[26918:423496] queue2->i:0
2019-07-05 15:44:40.743625+0800 GCD2[26918:423496] queue2->i:1
2019-07-05 15:44:40.743625+0800 GCD2[26918:423494] queue1->i:1
2019-07-05 15:44:40.743645+0800 GCD2[26918:423504] queue3->i:1
2019-07-05 15:44:40.846249+0800 GCD2[26918:423494] queue1->i:2
2019-07-05 15:44:40.846269+0800 GCD2[26918:423504] queue3->i:2
2019-07-05 15:44:40.846279+0800 GCD2[26918:423496] queue2->i:2

总结:先一次创建线程3 4 5,后交替执行任务(可理解为任务在同时进行)。线程创建耗时远小于任务耗时。

3、串行队列+同步任务

依次创建线程,依次执行任务,同步任务不会创建新的线程,直接加入到主线程中。

/**********串行队列+异步任务**********/
-(void)serial_sync{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue1->i:%d",i);//打印任务
        }
    });
    dispatch_sync(queue, ^{
        NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue2->i:%d",i);//打印任务
        }
    });
    dispatch_sync(queue, ^{
        NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue3->i:%d",i);//打印任务
        }
    });
}

总结:和并行队列+同步任务一样有同步任务就不会创建新的线程,在主线程中依次执行。

4、串行队列+异步任务

一次只允许创建一个线程,异步任务开辟新的线程,因此开辟新线程,顺序执行任务。

/**********串行队列+同步任务**********/
-(void)serial_async{
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue1->i:%d",i);//打印任务
        }
    });
    dispatch_async(queue, ^{
        NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue2->i:%d",i);//打印任务
        }
    });
    dispatch_async(queue, ^{
        NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
        for (int i=0; i<3; i++) {
            [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
            NSLog(@"queue3->i:%d",i);//打印任务
        }
    });
}

执行结果:

2019-07-05 16:15:21.689644+0800 GCD2[27190:438198] queue1:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:21.789806+0800 GCD2[27190:438198] queue1->i:0
2019-07-05 16:15:21.890003+0800 GCD2[27190:438198] queue1->i:1
2019-07-05 16:15:21.990909+0800 GCD2[27190:438198] queue1->i:2
2019-07-05 16:15:21.991172+0800 GCD2[27190:438198] queue2:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:22.095481+0800 GCD2[27190:438198] queue2->i:0
2019-07-05 16:15:22.199444+0800 GCD2[27190:438198] queue2->i:1
2019-07-05 16:15:22.301926+0800 GCD2[27190:438198] queue2->i:2
2019-07-05 16:15:22.302171+0800 GCD2[27190:438198] queue3:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:22.402858+0800 GCD2[27190:438198] queue3->i:0
2019-07-05 16:15:22.507079+0800 GCD2[27190:438198] queue3->i:1
2019-07-05 16:15:22.609571+0800 GCD2[27190:438198] queue3->i:2

总结:创建新的线程3,因为顺序执行一个任务结束后线程被回收,下一个任务执行前创建的线程number依然是3

5、主队列+同步任务(死锁)

主线程的特点:主线程会先执行主线程上的代码段(代码段结束),然后才执行主队列上的任务。
同步执行函数dispatch_sync特点:等待任务执行完成才结束。
这样主线程等待代码段返回,才执行任务,而代码段dispatch_sync要等任务完成才能返回。构成了相互等待,造成死锁。
运行代码:

/**********主队列+同步任务**********/
-(void)main_sync{
   NSLog(@"%@",[NSThread currentThread]);
   dispatch_queue_t queue = dispatch_get_main_queue();
   dispatch_sync(queue, ^{
       NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
       for (int i=0; i<3; i++) {
           [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
           NSLog(@"queue1->i:%d",i);//打印任务
       }
   });
}

执行崩溃。

wait.png

6、其他线程中执行主队列+同步任务(解锁)

注意在其他线程中执行主线程+同步队列,不会造成死锁,原因是main+sync放入到其他线程,而其他线程任务被放在主线程,这时候主线程不需要等待同步代码段执行结束,而去直接处理任务。任务处理完成,同步代码段也就执行完成,打破之前的死锁。代码如下:

/**********主队列+同步任务 在其他线程中执行**********/
-(void)main_sync{
    dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
            for (int i=0; i<3; i++) {
                [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
                NSLog(@"queue1->i:%d",i);//打印任务
            }
        });
    });
}

执行结果:

2019-07-05 17:31:58.821279+0800 GCD2[27883:509852] <NSThread: 0x600001d97b80>{number = 3, name = (null)}
2019-07-05 17:31:58.833359+0800 GCD2[27883:509700] queue1:<NSThread: 0x600001dc6940>{number = 1, name = main}
2019-07-05 17:31:58.933592+0800 GCD2[27883:509700] queue1->i:0
2019-07-05 17:31:59.034285+0800 GCD2[27883:509700] queue1->i:1
2019-07-05 17:31:59.135283+0800 GCD2[27883:509700] queue1->i:2

7、串行队列+异步任务嵌套同步任务(死锁)

串行队列异步任务中嵌套同步任务同样会出现死锁问题。

/**********串行队列+异步任务嵌套同步任务(死锁)**********/
-(void)main_sync{
    dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
            for (int i=0; i<3; i++) {
                [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
                NSLog(@"queue1->i:%d",i);//打印任务
            }
        });
    });
}
  • 串行特点:等待任务完成执行队列其他任务
  • 串行异步任务dispatch_async等待内部任务dispatch_sync结束
  • 串行同步任务dispatch_sync等待上一级任务dispatch_async结束
  • dispatch_async和dispatch_sync相互等待构成死锁

四、GCD其他应用

1、单例,特性在整个进程运行期间只创建一次,对象唯一。

使用dispatch_once

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
        
});

dispatch_once代码块中代码只执行一次。

2、延时

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"延时1.0秒执行任务");
});

其他延时:

[self performSelector:@selector(task) withObject:nil afterDelay:1.0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

3、栅栏函数

等待队列任务执行完成才执行后面的函数,只能在并行队列中使用。

dispatch_barrier_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"我是一个栅栏函数");
});

4、GCD定时器,不受Runloop影响

__block int time = 0;
//创建一个定时器,time必须长期持有,否则代码执行完就被释放
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0*NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    time++;
    NSLog(@"计时:%d",time);
});
dispatch_resume(timer);

5、GCD队列组dispatch_group_t

多个任务执行完后,才统一返回主线程处理:

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end");

执行结果:

2019-07-05 18:51:42.829144+0800 GCD2[31804:1055905] end
2019-07-05 18:51:42.933168+0800 GCD2[31804:1055937] 我是任务2
2019-07-05 18:51:42.933421+0800 GCD2[31804:1055936] 我是任务1
2019-07-05 18:51:43.033894+0800 GCD2[31804:1055905] 任务一任务二都执行完成

比如空间上传图片,上传完成后回调通知主线程已完成。下载视频,打包处理等需要。

6、dispatch_group_wait

暂停当前线程,当前group执行完成才继续要下执行。

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"任务一任务二都执行完成");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end");

执行结果:

2019-07-05 18:50:47.440209+0800 GCD2[31779:1054875] 我是任务2
2019-07-05 18:50:47.543378+0800 GCD2[31779:1054909] 我是任务1
2019-07-05 18:50:47.543378+0800 GCD2[31779:1054911] end
2019-07-05 18:50:47.644511+0800 GCD2[31779:1054875] 任务一任务二都执行完成

对比上面运行结果,end在任务完成之后才执行,卡在wait后。

7、dispatch_group_enter、dispatch_group_leave

表示进入的任务和离开的任务,dispatch_group_enterdispatch_group_leave成对出现,当进入的个数等于离开任务的个数才会解除dispatch_group_wait阻塞,执行dispatch_group_notify函数。类似于操作系统上的pv操作,对信号量做加减操作,只有最终信号量为0时,解除阻塞。

dispatch_group_enter: 任务忙(任务进行中)计数加一;
dispatch_group_leave: 任务结束计数减一。

下面看一下只做计数加一的操作:

dispatch_group_enter(group);//任务计数加一
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end”);

打印结果:

2019-07-08 19:37:17.857429+0800 GCD2[993:19404] end
2019-07-08 19:37:17.960956+0800 GCD2[993:19446] 我是任务2
2019-07-08 19:37:17.960963+0800 GCD2[993:19445] 我是任务1

任务执行完后未能进入到dispatch_group_notify中,为阻塞态,需要在任务结束后做任务计数减一操作。如下代码:

dispatch_group_enter(group);//任务计数加一
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务1");
    dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    [NSThread sleepForTimeInterval:0.1];//模拟耗时操作
    NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end");

打印结果:

2019-07-08 19:43:49.960294+0800 GCD2[1074:22837] end
2019-07-08 19:43:50.062250+0800 GCD2[1074:22890] 我是任务1
2019-07-08 19:43:50.062253+0800 GCD2[1074:22889] 我是任务2
2019-07-08 19:43:50.162760+0800 GCD2[1074:22837] 任务一任务二都执行完成

任务结束计数减一后,阻塞立刻解除。

8、线程安全

多个线程读写同一个或一组数据,如果数据变化的结果和单线程是一致的我们就说线程是安全的,如果不一致则线程不安全,我们也叫原子性(线程安全)非原子性(线程不安全)。

一般后台设计到的线程安全会相对多一点,比如对商品库存数减一,首先要加锁,排队操作,比如两个人同时下单,如果没有加锁会出现,在两个线程中同时做stock-1操作,这时的stock100,则最终得到的结果都是99,被插入到数据库,明明一百件确出来了200300的订单,库管顿时就上火了。下面就模拟一下多线程库存操作。

非原子操作:需要在不同组创建多个线程

//非线程安全
-(void)semaphore{
    self.stock = 100;
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_queue_create("stock_queue1", DISPATCH_QUEUE_SERIAL), ^{
        NSLog(@"%@",[NSThread currentThread]);
        [weakSelf reduceStock];
    });
    dispatch_async(dispatch_queue_create("stock_queue2", DISPATCH_QUEUE_SERIAL), ^{
        NSLog(@"%@",[NSThread currentThread]);
        [weakSelf reduceStock];
    });
}
//减库存
-(void)reduceStock{
    static int num = 0;//记录减库存次数,理论要求num=50
    while (self.stock>0) {
        if (self.stock>0) {
            num++;//减库存次数
            self.stock--;//实际库存操作
            NSLog(@"stock:%d",self.stock);
            [NSThread sleepForTimeInterval:0.01];//模拟耗时操作
        }else{
            NSLog(@"库存为零");
            break;
        }
    }
    NSLog(@"减操作:%d次",num);
}

截取一部分打印结果:

2019-07-08 20:37:54.562600+0800 GCD2[1780:51291] stock:97
2019-07-08 20:37:54.665220+0800 GCD2[1780:51291] stock:95
2019-07-08 20:37:54.665220+0800 GCD2[1780:51290] stock:95
2019-07-08 20:37:54.769379+0800 GCD2[1780:51290] stock:94
2019-07-08 20:37:54.769379+0800 GCD2[1780:51291] stock:94
……
2019-07-08 20:37:59.810830+0800 GCD2[1780:51291] num:106
2019-07-08 20:37:59.915351+0800 GCD2[1780:51290] num:106

发现以上打印结果,出现库存未正常减一,库存减操作共106次,实际库存100,与实际要求不符,因此该操作是非线程安全的。不可在实际应用中使用。

线程安全操作:添加semaphore

//线程安全
-(void)semaphore{
    self.stock = 100;
    semaphore_lock = dispatch_semaphore_create(1);
    
    __weak typeof (self) weakSelf = self;
    dispatch_async(dispatch_queue_create("stock_queue1", DISPATCH_QUEUE_SERIAL), ^{
        NSLog(@"%@",[NSThread currentThread]);
        [weakSelf reduceStock];
    });
    dispatch_async(dispatch_queue_create("stock_queue2", DISPATCH_QUEUE_SERIAL), ^{
        NSLog(@"%@",[NSThread currentThread]);
        [weakSelf reduceStock];
    });
}
//减库存
-(void)reduceStock{
    static int num = 0;//记录减库存次数,理论要求num=50
    while (self.stock>0) {
        dispatch_semaphore_wait(semaphore_lock, DISPATCH_TIME_FOREVER);
        if (self.stock>0) {
            num++;//减库存次数
            self.stock--;//实际库存操作
            NSLog(@"stock:%d",self.stock);
            [NSThread sleepForTimeInterval:0.01];//模拟耗时操作
            dispatch_semaphore_signal(semaphore_lock);
        }else{
            NSLog(@"库存为零");
            dispatch_semaphore_signal(semaphore_lock);
            break;
        }
    }
    NSLog(@"减操作:%d次",num);
}

打印结果:

019-07-08 20:57:36.846273+0800 GCD2[2087:63260] stock:99
……
2019-07-08 20:57:36.857036+0800 GCD2[2087:63259] stock:1
2019-07-08 20:57:36.867285+0800 GCD2[2087:63260] stock:0
2019-07-08 20:57:36.878032+0800 GCD2[2087:63259] 库存为零
2019-07-08 20:57:36.878031+0800 GCD2[2087:63260] 减操作:100次
2019-07-08 20:57:36.878163+0800 GCD2[2087:63259] 减操作:100次

数据正常,对stock的操作是按顺序执行的,100库存只减100次,符合实际需求。库管大哥再也不用慌张了。

提到信号量补充一个对象锁NSLock

NSLock:对象锁,保证线程安全。加锁的目的和信号量一样,保证线程安全,保证数据异步操作结果和同步操作的结果一致。

代码如下:

/*
 NSLock:对象锁,两个或多个线程对一个对象进行访问时需要需要对对象加锁
 */
-(void)lock{
    NSLock *lock = [[NSLock alloc] init];
    //创建一个异步任务
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        sleep(2);
        NSLog(@"任务一");
        [lock unlock];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        NSLog(@"任务二");
        [lock unlock];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        NSLog(@"任务三");
        [lock unlock];
    });
}

执行结果:

2019-07-12 22:14:53.902804+0800 GCD2[2441:52502] 任务一
2019-07-12 22:14:53.903090+0800 GCD2[2441:52504] 任务二
2019-07-12 22:14:53.903246+0800 GCD2[2441:52503] 任务三

以上在任务一未解锁前,任务二前的加锁操作被阻塞,等待任务一解锁,解锁后立刻往下执行,任务三也是如此。

在不加锁的情况下,去掉代码中的对象锁得到的结果如下:

2019-07-12 22:23:29.414548+0800 GCD2[2540:56525] 任务三
2019-07-12 22:23:29.414548+0800 GCD2[2540:56523] 任务一
2019-07-12 22:23:29.414550+0800 GCD2[2540:56526] 任务二

任务执行是同步进行的,所以谁先完成不确定。

如果同时操作一个对象属性,在未赋值前都拿到了属性值num=0,这时对数据做+1运算,那么三次运算后得到的最终结果是1,则该情况下线程是不安全的
代码如下:

-(void)nolock{
    __block int num = 0;
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //创建一个异步任务
    dispatch_group_async(group, queue, ^{
        //模拟取属
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务一");
    });
    dispatch_group_async(group, queue, ^{
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务二");
    });
    dispatch_group_async(group, queue,  ^{
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务三");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"num:%d",num);//结果必须是1
    });
}

打印结果:

2019-07-12 22:40:25.545517+0800 GCD2[2774:65690] 任务二
2019-07-12 22:40:25.545536+0800 GCD2[2774:65692] 任务一
2019-07-12 22:40:25.545496+0800 GCD2[2774:65691] 任务三
2019-07-12 22:40:25.545992+0800 GCD2[2774:65692] num:1

执行结果和预期不一致,则说线程是不安全的,在实际应用中任务执行耗时是不同的,所以执行结果也是不确定的。

线程加锁如下:

-(void)lock{
    __block int num = 0;
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    NSLock *lock = [[NSLock alloc] init];
    //创建一个异步任务
    dispatch_group_async(group, queue, ^{
        [lock lock];
        //模拟取属
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务一");
        [lock unlock];
    });
    dispatch_group_async(group, queue, ^{
        [lock lock];
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务二");
        [lock unlock];
    });
    dispatch_group_async(group, queue,  ^{
        [lock lock];
        int tmp = num;
        sleep(1);//模拟耗时操作
        num = tmp+1;
        NSLog(@"任务三");
        [lock unlock];
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"num:%d",num);//结果必须是一
    });
}

输出:

2019-07-12 22:56:20.602732+0800 GCD2[3017:73942] 任务一
2019-07-12 22:56:21.607104+0800 GCD2[3017:73940] 任务二
2019-07-12 22:56:22.610856+0800 GCD2[3017:73941] 任务三
2019-07-12 22:56:22.611203+0800 GCD2[3017:73941] num:3

这时候对数据操作是顺序执行的,结果为预想结果,线程安全。

相关文章

网友评论

      本文标题:GCD部分总结

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