05线程

5.1 GCD

  • GCD是一套多线程库,可以有效的替换NSThread或者NSOperation。它的基本结构是dispatch_async(queue, block);参数中的queue可以通过dispatch_queue_create或者系统提供的标准dispatch queue。
    // 生成一个serial dispatch queue
    dispatch_queue_t serialQueue = dispatch_queue_create("com.demo.sai", NULL);
    // 生成一个concurrent dispatch queue
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.demo.sai", DISPATCH_QUEUE_CONCURRENT);

    // 生成的dispatch queue需要手动release,注意ARC不会释放dispatch_queue_t类型的变量
    dispatch_release(serialQueue);
    dispatch_release(concurrentQueue);

    // 使用系统已经提供的方法来create queue
    dispatch_queue_t serialQueue2 = dispatch_get_main_queue();
    dispatch_queue_t concurrentQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 因此真正在代码中经常是这样写的
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"execute in main thread");
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"execute in a concurrent thread");
    });

  • GCD中线程分为Serial Dispatch Queue和Concurrent Dispatch Queue,分别为顺序执行和并发执行。在使用dispatch_get_main_queue时获得的是主线程queue,因此它一定是顺序执行的。使用dispatch_get_global_queue获得的queue所能并行的线程数量由系统来确定,并且可以甚至优先级,然后由于XNU内核用于Global Dispatch Queue的线程不保证实时性,因此执行优先级只是大致的判断(说白了就是并不是严格执行的)。

  • dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);可以用来改变queue的优先级先度

// 变更queue的priority
dispatch_set_target_queue(concurrentQueue, serialQueue);
  • 延迟将block添加到queue中,dispatch_after
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"dispatch_after");
    });

<font size=3 color=red>注意这个方法可以用来做指定一个时间后执行某段代码但是这个时间参数并不准确,因为这个只是在一段时间后将block加入到queue中,但是并不意味着马上执行。</font>

PS:关于用到的时间的宏的说明

NSEC:纳秒。

USEC:微妙。

SEC:秒

PER:每

所以:

NSEC_PER_SEC,每秒有多少纳秒。

USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)

NSEC_PER_USEC,每毫秒有多少纳秒。

所以,延时1秒可以写成如下几种:

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);

dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);

dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

最后一个“USEC_PER_SEC * NSEC_PER_USEC”,翻译过来就是“每秒的毫秒数乘以每毫秒的纳秒数”,也就是“每秒的纳秒数”

  • dispatch_group,当在并发完成一些处理后,可能需要一个节点来完成某个操作,这个操作必须等到之前的处理全部完成,那么就需要用到dispatch_group来实现。配合dispatch_group使用的有两个,dispatch_group_notifydispatch_group_wait,前者是当处理全部完成后执行,后者是等待一段时间后,用返回值判断是否所有的任务都完成了。当等待时间为DISPATCH_TIME_FOREVER时返回值一定会是0。另外,dispatch_group_wait会阻塞调用的线程。
       dispatch_queue_t groupQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();

    // 第一个处理
    dispatch_group_async(group, groupQueue, ^{
        NSLog(@"first");
    });

    // 第二个处理
    dispatch_group_async(group, groupQueue, ^{
        NSLog(@"secend");
    });

    // 完成后最终的处理
    dispatch_group_notify(group, groupQueue, ^{
        NSLog(@"done");
    });

    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (result == 0) {
        NSLog(@"done");
    }
    else {
        NSLog(@"not done");
    }
    // 需要release
    dispatch_release(group);

-dispatch_barrier_async,在代码中会等待之前加入某个queue的block全部执行完,然后使得concurrent queue变为一个serial queue,只能执行用dispatch_barrier_async添加的block,等完成后,queue变回并发。典型应用场景DB read的操作使用dispatch_async,当需要write DB时使用dispatch_barrier_async,可以保证数据的一致性,也相当于给写操作加了锁(好吧这样说不够严谨)。

-dispatch_sync,同步的将block放到某个queue中,执行这个函数的线程会阻塞等待block执行完成。很容易导致死锁,目前看最好别用。

关于dispatch_sync导致死锁的问题:
dispatch_sync(dispatch_get_main_queue, ...)这样写一定会死锁,dispatch_(a)sync这两个函数本质上是将block放到一个queue中,只不过一个会阻塞当前调用函数的线程,一个不会。

dispatch_async(dispatch_get_main_queue, ...)假设是main thread执行这个函数,那么线程不会等待block执行,虽然这个block是在main thread中执行的,最有可能的是在下一个loop中才会执行block。

dispatch_async(dispatch_get_global_queue(...), ...)假设是main thread执行这个函数,那么线程不会等待block执行,而是由系统分配另一个线程完成block,这个方式就是典型的多线程。

dispatch_sync(dispatch_get_main_queue, ...)假设是main thread执行这个函数,那么线程会等待block执行,但是这个block又是在main thread执行的,导致死锁。

dispatch_sync(dispatch_get_global_queue(...), ...)假设是main thread执行这个函数,那么线程会等待block执行,由系统分配另一个线程完成block,这个方式可用,但是最好不用main thread,而是自己创建一个serial queue。

  • 最后看一组代码的结果
  // 使用系统已经提供的方法来create queue
    dispatch_queue_t serialQueue2 = dispatch_get_main_queue();
    dispatch_queue_t concurrentQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 因此真正在代码中经常是这样写的
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"execute in main thread");
    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"execute in a concurrent thread");
    });

    // 变更queue的priority
    //    dispatch_set_target_queue(concurrentQueue, serialQueue);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"dispatch_after");
    });

    dispatch_queue_t groupQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();

    // 第一个处理
    dispatch_group_async(group, groupQueue, ^{
        NSLog(@"first");
    });

    // 第二个处理
    dispatch_group_async(group, groupQueue, ^{
        NSLog(@"secend");
    });

    // 完成后最终的处理
    dispatch_group_notify(group, groupQueue, ^{
        NSLog(@"done");
    });

    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (result == 0) {
        NSLog(@"result done");
    }
    else {
        NSLog(@"result not done");
    }
    // 需要release
    dispatch_release(group);

    dispatch_async(concurrentQueue2, ^{
        NSLog(@"read1");
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"read2");
    });

    dispatch_barrier_async(concurrentQueue2, ^{
        NSLog(@"write");
    });

    dispatch_async(concurrentQueue2, ^{
        NSLog(@"read3");
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"read4");
    });

2016-07-09 14:46:28.500 CRHelper[3027:231293] execute in a concurrent thread
2016-07-09 14:46:28.500 CRHelper[3027:231294] first
2016-07-09 14:46:28.500 CRHelper[3027:231297] secend
2016-07-09 14:46:28.502 CRHelper[3027:231297] done
2016-07-09 14:46:28.502 CRHelper[3027:231205] result done
2016-07-09 14:46:28.507 CRHelper[3027:231297] read1
2016-07-09 14:46:28.507 CRHelper[3027:231293] read2
2016-07-09 14:46:28.507 CRHelper[3027:231390] write
2016-07-09 14:46:28.507 CRHelper[3027:231294] read3
2016-07-09 14:46:28.507 CRHelper[3027:231297] read4
2016-07-09 14:46:28.546 CRHelper[3027:231205] execute in main thread
2016-07-09 14:46:31.782 CRHelper[3027:231294] dispatch_after

"execute in main thread"这句话在代码顺序中是第一个但是却在倒数第二个输出,因为是在main thread执行的,可以很明确的看出来这个代码是在下一个loop中执行的,还有就是dispatch_after也并不是严格的按照3秒后执行的.

  • dipatch_apply的应用场合主要是循环在一个queue中调用某个block,可以用于处理集合。这个函数会想wait一样阻塞线程,因此在非主线程中用比较好。
    NSArray* arr = [NSArray arrayWithObjects:@1, @2, nil];
    for (NSInteger i = 0; i < [arr count]; i++) {
        NSLog(@"%@", [arr objectAtIndex:i]);
    }
    dispatch_async(concurrentQueue2, ^{
        dispatch_apply([arr count], concurrentQueue2, ^(size_t i) {
            NSLog(@"%@", [arr objectAtIndex:i]);
        });
    });
  • 拾遗:dipatch_semaphoredipatch_suspenddipatch_resume等。基本没用过,懒得写了。

5.2 NSThread和performSelector

  • 看一组代码和结果。可以看出performSelector方法是在main线程执行,而且performSelectorOnMainThread这个方法的输出在4之后,应该是这个方法会在下一个main thread的runloop中执行。1和4在测试中先后顺序不定,也比较好理解。
- (void)doSome:(NSString*)arg
{
    NSLog(@"%@ -> %@", [NSThread currentThread], arg);
}

- (void)startThread
{
    NSThread* th = [[NSThread alloc] initWithTarget:self selector:@selector(doSome:) object:@"1"];
    BOOL state = [th isMainThread];
    state = [th isCancelled];
    state = [th isFinished];
    state = [th isExecuting];

    [th start];

    [self performSelectorOnMainThread:@selector(doSome:) withObject:@"2" waitUntilDone:NO];
    [self performSelector:@selector(doSome:) withObject:@"3" afterDelay:3];

    [self performSelector:@selector(doSome:) withObject:@"4"];
}
2016-08-28 22:40:45.055 ARCTest[3619:236163] <NSThread: 0x7fc0c3501b70>{number = 1, name = main} -> 4
2016-08-28 22:40:45.055 ARCTest[3619:236310] <NSThread: 0x7fc0c3723f30>{number = 2, name = (null)} -> 1
2016-08-28 22:40:45.077 ARCTest[3619:236163] <NSThread: 0x7fc0c3501b70>{number = 1, name = main} -> 2
2016-08-28 22:40:48.056 ARCTest[3619:236163] <NSThread: 0x7fc0c3501b70>{number = 1, name = main} -> 3

5.3 NSOperation

5.4 锁

  • 使用:NSLock, @synchronized(self),或者使用DISPATCH_QUEUE_SERIAL这样的形式来实现锁。

  • 比较:

NSLock控制麻烦,而且要考虑死锁。

@synchronized(self)如果很多方法都用self做锁,那么会导致一个长时间执行的方法阻塞其它方法,所以如果用最好不要都使用一个对象来作为锁对象。

GCD的锁不需要关注实现,而且是深层次实现的,比较高效,并且方法众多,可以时间多种锁的需要。

<span id="choose">5.5 多线程编程的选择</span>

  • NSThread功能完善,比较基础,能够完成简单的任务。当需要同步执行或者有一定的依赖要求时编程较为复杂。
  • performSelector缺点很多,首先执行SEL这个东西,需要判定方法是否存在;第二、在ARC下使用如果方法创建并返回一些对象,此时如果不进行一定的处理会导致内存泄露,因为ARC下默认是不做对象的autorelease的。第三、参数传递个数有限,有时需要自己定义个对象传递多个参数。
  • NSOperation,功能强大尤其是对于依赖性的处理使用起来很方便。
  • GCD,其实使用GCD基本可以完成任何任务,包括有依赖性的任务,和NSOperation搭配起来灵活使用吧。
  • GCD和NSOperation的对比:

1)GCD是纯C语言的API,而操作队列则是Object-C的对象。

2)在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构;相反操作队列中的『操作』NSOperation则是个更加重量级的Object-C对象。

3)具体该使用GCD还是使用NSOperation需要看具体的情况

4)需要注意,如果直接使用NSOperation的start方法是直接在调用线程执行的,这意味着可能是在UI线程执行。

  • NSOperation和NSOperationQueue相对GCD的好处有:

1)NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。

2)NSOperation可以方便的指定操作间的依赖关系。

3)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)

4)NSOperation可以方便的指定操作优先级。操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的后执行。

5)通过自定义NSOperation的子类可以实现操作重用,

5.6 并发

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

推荐阅读更多精彩内容

  • 学习多线程,转载两篇大神的帖子,留着以后回顾!第一篇:关于iOS多线程,你看我就够了 第二篇:GCD使用经验与技巧...
    John_LS阅读 606评论 0 3
  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,716评论 0 17
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,016评论 0 4
  • .一.进程 进程:是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空...
    IIronMan阅读 4,476评论 1 33
  • 身处在广州这大城市已经二十多个年头了,在这土生土长的城市里,每一年总会给我不同的感受。 刚过去这一年,是最为特别的...
    星梦缘阅读 893评论 0 2