ios-多线程(NSThread,GCD,NSOperation)

线程:

英文:Thread
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。 -----百度百科

ios中实现多线程的几种方式:

  • Pthreads(不用)
  • NSThread(用一部分,其中几个比较方便的方法)
  • GCD(常用)
  • NSOperation&NSOperationQueue(看需求)
- Pthreads

pthread 是 POSIX 多线程开发框架,是基于C 语言的跨平台框架。没用过,不了解,感兴趣的同学可以自己度娘下。

- NSThread

NSThread是基于Thread使用,轻量级的多线程编程方法,一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。所以一般只使用其中几个方法,方便调试线程。
[NSThread isMainThread]; // 是否主线程
[NSThread currentThread]; // 当前线程

- GCD

Apple基本c++开发的一套多线程处理技术,自动管理生命周期。

任务与队列

任务:你要执行的操作,GCD将任务放在block中。执行任务有2中方式,同步执行,异步执行。

  • 同步
    不具备开启线程的能力,会阻塞当前线程。
  • 异步
    具备开启新线程的能力,不会阻塞当前线程。

队列:用来存放任务的队列,是一种特殊的线性表,采用FIFO(先进先出)的原则,则从顶部开始读取任务,从尾部加入任务到队列。在GCD中有3种队列:串行队列,并行队列,主队列(特殊的串行队列)。

  • 串行队列
    一个一个任务有序执行,上一个任务没执行完毕,下一个任务不会执行。
  • 并行队列
    同时执行多个任务,不用等待上一个任务执行完毕。
  • 主队列
    和串行队列一样,需要等待上一个任务执行完成,才能执行下一个任务。
创建队列:
  • 全局并行队列(系统自带的,全局唯一)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 自定义并行队列:
dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
  • 自定义串行队列:
dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
  • 主队列:
dispatch_get_main_queue()
创建任务:
  • 同步任务
dispatch_sync(队列, ^{要执行的任务});
  • 异步任务
dispatch_async(队列, ^{要执行的任务});

基本使用:

  • 同步任务+串行队列(不会开启新线程,在当前线程中执行任务)
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });
  • 同步任务+并行队列(不会开启新线程,在当前线程中执行任务)
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });
  • 同步任务+主队列(不会开启新线程,在主线程中执行任务)
dispatch_sync(dispatch_get_main_queue(), 0), ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });
  • 异步任务+串行队列(会开启一条新的线程,串行执行任务)
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });
  • 异步任务+并行队列(会开启至少一条新的线程,并行执行任务)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });
  • 异步任务+主队列(不会开启新的线程,会在主线程中执行任务)
dispatch_async(dispatch_get_main_queue(), 0), ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
 });

总结:
1.同步任务都不会开启新的线程,所以会阻塞当前线程。
2.主队列中的任务不会开启新的线程,会在主线程中执行。
3.异步任务+串行队列,会开启一条新的线程,在新的线程串行执行任务。
4.异步任务+并行多列,会开启至少一条新的线程,在新的线程中并发执行任务。

线程阻塞:

实例1:

NSLog(@"当前线程----->%@",[NSThread currentThread]); // 会打印
dispatch_sync(dispatch_get_main_queue(), ^{
     NSLog(@"当前线程----->%@",[NSThread currentThread]);  // 这句话永远不会打印,此时主线程已经阻塞了,你对界面的所有操作都没反应了。
});
NSLog(@"当前线程----->%@",[NSThread currentThread]); // 不会打印

原因:
1.dispatch_sync同步任务,不会开启新的线程,所以上面的代码是在主线程中执行的,也就会阻塞主线程,等待block中的任务完成。
2.dispatch_get_main_queue()主队列,会把block中的任务放进主队列,也就是主线程中去执行,可是此时主线程已经阻塞了,block永远无法完成任务。所以就会一直阻塞主线程。

实例2:

    // 自定义串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    
    // 串行异步
    dispatch_async(queue, ^{  // @1
        NSLog(@"当前线程----->%@",[NSThread currentThread]); // 会打印
        dispatch_sync(queue, ^{ // 不会打印  @2
             NSLog(@"当前线程1----->%@",[NSThread currentThread]);
        });
        NSLog(@"当前线程2----->%@",[NSThread currentThread]); // 不会打印
    });
    NSLog(@"当前线程3----->%@",[NSThread currentThread]); // 会打印 @3

原因:
1.上面的代码@1会开启一条新的线程,但因为是在串行队列中,所以会一个一个执行任务。我们假设开启的新线程叫“B”;
2.打印完"当前线程"后,@2同步任务,不会开启新的线程,会阻塞当前线程,所以还是在"B"线程中执行任务,此时线程"B"已经阻塞了,@2会把block当中的任务放入"myQueue"中去执行,但是"myQueue"是串行的,所以必须等"myQueue"执行完上一个任务,而它执行的上一个任务就是当前block中的任务,也就是阻塞了的@2,@2永远执行不了,所以会一直阻塞。
3.@3会打印,是因为它是在主线程中的。

实例3:

    // 创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{ // @1
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{ // @2
            NSLog(@"当前线程1----->%@",[NSThread currentThread]);
        });
        NSLog(@"当前线程2----->%@",[NSThread currentThread]); // @3
    });
    NSLog(@"当前线程3----->%@",[NSThread currentThread]); // @4
    -----------------------------以上都会打印-----------------------------

原因:
1.@1会开启至少一条新的线程,并行执行任务。
2.@2不会开启新的线程,在当前线程并行执行任务。会阻塞当前线程,但因为是并行队列中,所以会执行完@2,在执行@3.
3.@3在主线程中执行,不受影响。

队列组

    // 创建组
    dispatch_group_t group = dispatch_group_create();
    // 系统全局唯一并行队列
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 自定义串行队列,按顺序执行
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_group_async(group, queue, ^{ // @1
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{ // @2
        NSLog(@"当前线程1----->%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{ // @3
        NSLog(@"当前线程2----->%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, queue, ^{ // @4
        NSLog(@"当前线程3----->%@",[NSThread currentThread]);
    });
  • 并行队列
    @1,2,3会随机打印,最后打印@4
  • 串行队列
    @1<@2<@3<@4 按顺序打印

单列

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

延时执行

    // 如果在串行、并行队列中执行,会开启线程。也就是说dispatch_after方法是异步执行的
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
    });

栅栏方法(分割任务)

    // dispatch_barrier_async 方法需使用自定义队列,不能使用系统全局队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{ // @1
        NSLog(@"当前线程----->%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{ // @2
        NSLog(@"当前线程1----->%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{ // @3
        NSLog(@"当前线程2----->%@",[NSThread currentThread]);
    });

注意:
使用dispatch_barrier_async时:
1.必须使用自定义队列,不能使用系统全局队列。
dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)


NSOperation&NSOperationQueue

NSOperation是Apple对GCD的封装,是面向对象的。NSOperation、NSOperationQueue分别对应GCD中的任务和队列。

注意:NSOperation是个抽象类,不能直接使用,必须使用它的2个子类:NSInvocationOperation、NSBlockOperation。

创建任务:

  • NSInvocationOperation
NSInvocationOperation *invocationOP = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[invocationOP start];
  • NSBlockOperation
 NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程---->%@",[NSThread currentThread]);
 }];

[blockOP addExecutionBlock:^{
        NSLog(@"当前线程1---->%@",[NSThread currentThread]);
}];
[blockOP start];

注意:
1.addExecutionBlock方法可能开启新的线程,也可能在主线程中执行。
2.addExecutionBlock方法调用必须在start方法之前,否则会报错。

  • 自定义任务:新建一个类继承NSOperation,需要重写main,cancel,finished,executing等方法。

创建队列:(只有主队列,和其他队列,没有串行和并行区分)

  • NSOperationQueue(其他队列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  • 主队列
[NSOperationQueue mainQueue];
使用:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1; // 串行,默认为-1不限制,既并行
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程---->%@",[NSThread currentThread]);
 }];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程1---->%@",[NSThread currentThread]);
 }];
[queue addOperation:operation];
[queue addOperation:operation1];

注意:任务加入队列中,会自动执行,不需要调用start方法,否则会报错。

依赖

必须等A任务执行完毕之后在执行B任务。
比如从网上开启一个线程下载图片,必须等图片下载完成之后在主线程加载图片,刷新UI,这个时候就可以用上依赖了。

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //queue.maxConcurrentOperationCount = 1; // 串行,默认为-1不限制
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程---->%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程1---->%@",[NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前线程2---->%@",[NSThread currentThread]);
    }];
    
    [operation addDependency:operation1]; // operation依赖operation1
    [operation2 addDependency:operation]; // 2operation依赖operation
    [queue addOperations:@[operation,operation1] waitUntilFinished:NO];
    [NSOperationQueue.mainQueue addOperation:operation2];

2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 当前线程1----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 当前线程----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
2017-08-10 10:46:34.948 ZTSlidingVC[1273:222557] 当前线程2----><NSThread: 0x7fd9f3d287a0>{number = 1, name = main}

注意:依赖关系是可以跨队列的,如上面例子所示。

其他属性、方法

  • NSOperation
属性:
@property (readonly, getter=isCancelled) BOOL cancelled; // 是否取消任务
@property (readonly, getter=isExecuting) BOOL executing; // 是否正在执行
@property (readonly, getter=isFinished) BOOL finished; // 是否完成任务
方法:
- (void)cancel; // 取消任务
- (void)start; // 开始任务
- (void)addDependency:(NSOperation *)op; // 添加依赖
- (void)removeDependency:(NSOperation *)op; // 删除依赖
  • NSOperationQueue
属性:
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0); // 获取队列中的任务数量
@property NSInteger maxConcurrentOperationCount; // 设置最大任务数
@property (getter=isSuspended) BOOL suspended; // YES:暂停,NO:继续(对正在执行的任务无效,只是暂停调度新的任务执行)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0); // 获取当前队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0); // 获取主队列
方法:
- (void)cancelAllOperations; // 取消所有任务
- (void)waitUntilAllOperationsAreFinished; // 等待所有队列中的任务执行完成,会阻塞线程(在等待时,其他线程仍然可以往队列中添加任务)

线程同步

  • 为什么需要线程同步:
    当多个线程同时访问一个统一资源,造成数据状态不一致,产生的数据混乱,安全等问题。
  • 实现线程同步的2种方式:
    1.加锁
  1. @synchronized 关键字加锁
  2. NSLock 对象锁
  3. NSCondition
  4. NSConditionLock 条件锁
  5. NSRecursiveLock 递归锁
  6. pthread_mutex 互斥锁(C语言)
  7. dispatch_semaphore 信号量实现加锁(GCD)
  8. OSSpinLock

方法有点多,这就不一一介绍了,开发中也用不了这么多。这里就简单介绍一下1,2,7的使用把,需要其他更详细的的功能请自行goolge。

  • @synchronized 关键字加锁(性能较差,使用简单)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized (self) {
            NSLog(@"做你想做的事");
        }
 });
  • NSLock(性能一般)
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([lock tryLock]) { // 尝试加锁,如果失败了,并不会阻塞线程,只是立即返回NO
            NSLog(@"做你想做的事");
            [lock unlock]; // 记得解锁
        }
});
  • dispatch_semaphore 信号量实现加锁(GCD,推荐使用此方法)

dispatch_semaphore_create   创建一个semaphore
dispatch_semaphore_signal   发送一个信号(计数器+1)
dispatch_semaphore_wait    等待信号(信号量-1,如果信号量<=0,则一直等待,会阻塞线程)

dispatch_semaphore_t dsema = dispatch_semaphore_create(2); // 创建信号量,后面的数字既最大并发量
    for(int i=0; i<10; i++){
        dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);  // -1 DISPATCH_TIME_FOREVER会一直等待,直到信号量大于0。DISPATCH_TIME_NOW不等待,也就不能控制线程并发数了。
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"------>%d  当前线程---->%@",i,[NSThread currentThread]);
            dispatch_semaphore_signal(dsema); // +1
        });
    }

上面的列子,看起来创建了10个线程,其实同时只有2个线程在并发执行。

2.使用串行队列

参考:

http://www.jianshu.com/p/0b0d9b1f1f19
http://ksnowlv.github.io/blog/2014/09/07/ios-tong-bu-suo-xing-neng-dui-bi/

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

推荐阅读更多精彩内容