GCD中的使用(关于:队列、线程、任务的理解)

版权声明:本文为博主原创文章,未经博主允许不得转载。


2017/09/06
看到一篇关于线程锁的文章,十分的精彩。有兴趣的朋友可以点进去看看。

下面是几点自己关于线程锁的体会

  1. 什么是线程锁?
    一般情况下,多个进程可以同时访问一个内存。但如果中间穿插写操作就会发生一些异常。所以需要用线程锁,限制同一时刻只能有一个线程访问一个内存空间。

  2. 读写是如何的实现?(主要讲写)

. 首先从内存中读取数据到寄存器,i = 0
. 在寄存器中对数据进行写操作 i = 1
. 将寄存器中操作后的数据写入内存, i = 1

从这里可以看到如果A、B进行同时进行,A进行写数据,B进行读数据。则B读取到的数据可能是A写之前的数据也有可能是A写之后的数据

  1. 线程锁有哪些类型?

互斥锁、自旋锁、读写锁

互斥锁、自旋锁介绍

从 实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。
假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待


结论:

  1. async操作开辟子线程,sync操作不开辟子线程;
    2 . 同步会阻塞当前队列添加其他线程,异步不会阻塞当前队列。
  2. 并发队列可以同时执行多个线程任务,串行队列同时只能执行一个线程任务。

多线程

在iOS 中,操作多线程主要有4个方案:

  1. PThreads:它是C语言的框架,使用起来很不方便,基本不会用。

  2. NSThread:苹果对多线程进行了封装,是面向对象的。但因为它需要我们去管理线程的生命周期,所以用的也不是很多。不过用它去查看当前线程,或者让当前线程睡眠还是很方便的。

  3. 然后是GCD:GCD是苹果对多核运算提出的解决方案,而且gcd会自己管理生命周期,这样可以让我们把注意力集中在具体任务上。

  4. 最后一个是是NSOperation,它是对GCD封装。增加一些功能。比如线程都挂起和暂停,还有线程的依赖。

不过像我最常用的还GCD,对他也最了解。所以就讲一下它吧。

GCD新增的2个概念

任务:

//同步为主队列添加了一个任务,等到block结束后返回结果。没有创建一个新的线程
 dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
 });


//异步为主队列添加了一个任务,立刻返回结果,创建了一个新的线程执行任务
 dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"2");
 });

队列

* At the core, dispatch provides serial FIFO queues to which blocks may be
 * submitted. Blocks submitted to these dispatch queues are invoked on a pool
 * of threads fully managed by the system. No guarantee is made regarding
 * which thread a block will be invoked on; however, it is guaranteed that only
 * one block submitted to the FIFO dispatch queue will be invoked at a time.
 *
 * When multiple queues have blocks to be processed, the system is free to
 * allocate additional threads to invoke the blocks concurrently. When the
 * queues become empty, these threads are automatically released.

大概意思:dispatch_sync,dispatch_async中的block代码块(任务)会以FIFO的方式在队列中被调用。他们将会由系统从线程池中随机分配一个线程,然后执行任务。
ps:主队列的线程为主线程。

主队列:dispatch_get_main_queue()

全局队列: dispatch_get_global_queue(0, 0)

自定义队列:
    dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_SERIAL);//串行
    dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_CONCURRENT);//并发

并发队列:

  1. 任务以FIFO从序列中移除,然后并发运行,一次可以调度多个,可以按照任何顺序完成。
  2. 可以同时执行多个线程任务

串行队列:

  1. 任务以FIFO从序列中一个一个执行。一次只调度一个任务
  2. 而且只会开启一条线程

同步:

dispatch_queue_t  Aqueue = ~;

dispatch_sync(Aqueue, ^{
    NSLog(@"2");
 });

* dispatch_sync() will not return until the block has finished(同步为某队列添加了一个任务,要知道该任务返回才能返回。)

注意:
0. 不会创建新的线程,所以在当前的线程中执行
1. 该返回可以为dispatch_async() 的返回,也可以为任务执行完毕
2. 阻塞的是当前队列,而不是Aqueue队列;
3. 先阻塞当前队列,然后在向Aqueue队列中添加任务。(就是因为如此,所以在主队列中同步向主队列添加任务会造成死锁)

异步

dispatch_queue_t  Aqueue = ~;

dispatch_async(Aqueue, ^{
    NSLog(@"2");
 });

* Calls to dispatch_async() always return immediately after the block has
* been submitted, and never wait for the block to be invoked.
(立刻返回block结果,不需要等待block中的任务被调度)

注意:
   1.  会创建一个新的线程去执行任务
   2.  异步和同步一样,影响的是当前的队列,对Aqueue队列没有影响。

对同步异步、队列、任务的概念理解讲完了,下面通过例子来更进一步地理解。

- (void)test2{


    dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_SERIAL);//串行
    
//代码段1
    dispatch_async(serialQueue, ^{
        NSLog(@"1");
        sleep(3);
    });
    
//代码段2
    for(int i = 0; i < 5; i ++){
        NSLog(@"i = %d", i);
    }
    NSLog(@"0");
    
//代码段3
    dispatch_sync(serialQueue, ^{
        NSLog(@"2");
        sleep(1);
    });
    
//代码段4
    NSLog(@"4");
    
//代码段5
    dispatch_async(serialQueue, ^{
        NSLog(@"3");
    });
//代码段6
    NSLog(@"5");
 }

输出结果:
2017-02-27 15:38:14.945 test[2527:158657] i = 0
2017-02-27 15:38:14.945 test[2527:158694] 1
2017-02-27 15:38:14.945 test[2527:158657] i = 1
2017-02-27 15:38:14.945 test[2527:158657] i = 2
2017-02-27 15:38:14.945 test[2527:158657] i = 3
2017-02-27 15:38:14.946 test[2527:158657] i = 4
2017-02-27 15:38:14.946 test[2527:158657] 0
2017-02-27 15:38:17.950 test[2527:158657] 2
2017-02-27 15:38:18.952 test[2527:158657] 4
2017-02-27 15:38:18.952 test[2527:158657] 5
2017-02-27 15:38:18.952 test[2527:158694] 3

推断:serialQueue为一个串行队列,在串行队列中的任务执行FIFO。然后我看分析的代码:

  1. 代码1,异步为serialQueue添加一个任务(会开辟一个子线程执行),直接返回结果,所以同步执行代码段2。这里的执行结果返回顺序(也只能看结果)会随着任务消耗的时间来确定。此时主线程:代码段2;serialQueue线程:代码段1

  2. 代码3同步为serialQueue添加了打印2任务(不会开辟新的线程,所以在当前的主线程中执行),所以会阻碍当前队列的其他任务,主队列无法添加其他任务,等待打印2任务完成后执行代码4

  3. 码段5异步在serialQueue队列添加打印3任务,所以会同时执行代码6 。输出结果随着代码消耗时间决定,这里添加到子线程中执行的速度<执行代码6的速度。

列子二

MN3O8$(7(MAR`F0QZ7B(5(J.jpg

打印结果:1 ,3, 4, 2

// 17/2/28
解释:

  1. app启动的时候,会默认创建一个串行队列dispatch_get_main_queue(),我们称之为主队列,在主线程中调用。当前主队列有1个任务:打印1,异步为为全局队列添加一个A任务,打印3,4。
  2. A任务为异步在主线程中添加一个打印任务,所以当前主线程有2个任务(如果打印3,4没有结束的话)。根据FIFO,输出结果1,3,4 ;2。

例子3:

- (void)test6{
    
    dispatch_queue_t queue = dispatch_queue_create("金永峰", DISPATCH_QUEUE_CONCURRENT);

    //代码1
    dispatch_async(queue, ^{
        
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            for(int i = 0; i < 20; i ++){
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%@++++++++, %d", [NSThread currentThread], i);
            }
        });
        
    });
    
    //代码2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        //代码4
        dispatch_sync(queue, ^{
            for(int i = 0; i < 10; i ++){
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%@--------%d", [NSThread currentThread],i);
            }
        });
    });
}

打印结果:
2017-03-20 16:33:26.669 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------0
2017-03-20 16:33:26.669 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 0
2017-03-20 16:33:27.171 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 1
2017-03-20 16:33:27.171 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------1
2017-03-20 16:33:27.675 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 2
2017-03-20 16:33:27.675 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------2
2017-03-20 16:33:28.178 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------3

推断:

1.代码1为异步为queue添加任务,所以直接返回block结果,可以直接执行代码2,为全局创建1个任务。所以创建了2个任务(这一点要明确)。
     
2.queu队列里面有2个任务:1. 同步创建一个打印任务;2:打印---;2个任务在不同的线程中,并且queue是并发对; ,所以可以同时执行
     
3.全局队列中也有2个任务:1.同步创建1个打印任务,2.打印+++;2个任务在不同的线程中,并且全局队列是并发的,所以可以同时执行

例子4:

- (void)test7{
    
    dispatch_queue_t queue = dispatch_queue_create("金永峰", DISPATCH_QUEUE_CONCURRENT);
  
    //代码1
    dispatch_async(queue, ^{
        
        /** 的代码是queue中的一个完整的任务:打印\,同步在全局队列中创建打印+任务  */
        for(int i = 0; i < 20; i ++){
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"%@\\\\\\", [NSThread currentThread]);
        }
        
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            for(int i = 0; i < 20; i ++){
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%@++++++++, %d", [NSThread currentThread], i);
            }
        });
        
    });
    
    
    //代码2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        //代码4
        dispatch_sync(queue, ^{
            for(int i = 0; i < 10; i ++){
                [NSThread sleepForTimeInterval:0.5];
                NSLog(@"%@--------%d", [NSThread currentThread],i);
            }
        });
    });
}



推断:

1. 代码1为异步执行,所以直接返回block结果,可以直接执行代码2,所以创建了2个任务(这一点要明确,称为A、B任务)
     
首先:
2. queue队列中的任务有2个:1、打印\\\, 同步在全局队列中添加任务,2、打印+++。
     
3. 全局队列中的任务1个:同步创建1个任务
     
所以: 
 * 首先,queue(并发)中的执行代码:同时打印\\\和打印---;
 * 等到打印\\\结束(此时queue开始执行打印---任务),再同步在全局队列中添加任务;
 *最后全局队列执行任务打印++,queue执行任务打印--


推荐的一篇文章:并发 并行 同步 异步 多线程的区别, 多线程只是我们实现异步的一种手段


2017/09/26


    dispatch_async(dispatch_get_main_queue(), ^{
        [self logTask:@"mainQueue"];
    });
    
    dispatch_queue_t queue = dispatch_queue_create("cx", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        [self logTask:@"sync-queue"];
    });
    


//打印-- (模拟结果)

sync-queue
sync-queue
sync-queue
sync-queue
sync-queue
·
·
·
mainQueue
mainQueue
mainQueue
mainQueue

队列优先级

1. 自己创建的队列优先级要高于主队列

出现以上的结果是因为:

上面这段代码有创建了2个线程任务,其中有一个是同步,导致了主队列的死锁。所以第一个线程任务需要等待他的完成才能继续执行。

下面的例子中因为阻塞的不是主队列,所以可以打印任务是同步进行的。

    dispatch_async(queue, ^{
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
            [self logTask:@"async-DISPATCH_QUEUE_PRIORITY_LOW"];
        });
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self logTask:@"mainQueue2"];
    });

//打印-- (模拟结果)

mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_LOW
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
·
·
·
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_LOW
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW

另外,可以看出下面2个代码是不等价的

    dispatch_async(dispatch_get_main_queue(), ^{
        [self logTask:@"mainQueue2"];
    });
        [self logTask:@"mainQueue2"];
  1. 全局队列的优先级

DISPATCH_QUEUE_PRIORITY_HIGH >= DISPATCH_QUEUE_PRIORITY_DEFAULT > DISPATCH_QUEUE_PRIORITY_LOW > DISPATCH_QUEUE_PRIORITY_BACKGROUND

ps: 长耗时任务可能不会严格遵循优先级

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

推荐阅读更多精彩内容