iOS 多线程

概念

  • 进程(Process): 简单理解就是一个任务。对于iOS系统来说,一个进程可以认为就是一个app

  • 线程(Thread): 线程就是在进程中运行的子任务。共享一个进程中的所有资源(内存)。

一个进程可以包括多个线程。虽然线程共享进程中的资源,但是当一个资源被其中一个线程占用时,其他线程就没办法使用,需要等待其释放出来。

  • 同步(Sync): 按照顺序完成某事,会阻塞当前线程

  • 异步(Async): 同时做好几件事,调用后立即返回,不阻塞线程

线程的基本概念中,我个人觉得比较容易混淆的就是并发和并行这个两个:

  • 并发(Concurrency): 将相互独立的执行过程综合到一起。

  • 并行(Parallelism): 同时执行任务

  • 串行(Serial): 一次只能执行一个任务,必须等当前任务执行完毕后才能执行下一个任务

注意并发和并行之间的区别 并发指的是同时处理很多事情,并行指同时能够完成很多事情。前者重点是组合,我觉得可以理解为形容词,提供的是一种方式,后者重点是执行,这里就理解为动词,提供的是一种解决。两者相关,但是具体含义不同。

三种方式

在iOS中,创建线程的方式基本上有三种:

  • NSThread

  • NSOperation

  • GCD

这三种方法的封装性从上往下递增,也就是使用起来GCD最为方便,其次是NSOperation,最后是NSThread

NSThread

NSThread是最靠近底层的对象,所以使用起来比较麻烦,需要自己管理线程的声明周期,并且也没有实现线程状态等相关的功能。NSThread通常会用于一些比较轻量级的任务,比方iOS工程师都比较熟悉的performSelector:方法,就是属于NSThread的范畴。

如果想要实现自己的NSThread的话,可以继承NSThread并且重写main方法。如果重写了main的话,在调用其他继承的方法时就不用再加上super

创建线程

创建一个NSThread对象可以使用init或者是initWithTarget:selector:object:。第二个方法的最后一个参数用于传入selector对象所需的参数。

启动线程
  • + detachNewThreadSelector:toTarget:withObject:分发新的线程并且将指定的方法作为线程的入口。在执行期间,target和object都会被retained,当方法体执行完毕之后就会释放。

  • + detachNewThreadWithBlock:这个方法跟上面的类似,只不过是将selector变成了block来执行而已。

  • - start开启线程

  • - main为线程的入口点。不能直接调用,用于继承NSThread实现自己的线程类时,重写。

在启动线程的时候,如果该线程是app首次添加的线程对象的话,那么前三个方法会发送NSWillBecomeMultiThreadNotification通知。

停止线程
  • + sleepUntilDate: 线程休眠,直到参数指定的时间点。在阻塞线程期间,不会出现任何的runloop进程。

  • + sleepForTimeInterval:在指定的时间内进行线程休眠

  • + exit停止当前线程。exit方法会调用currentThread来获取当前的线程,然后停止之。在停止当前线程之前,会将当前的线程作为objectNSThreadWillExitNotificationpost出去。exit方法可能会造成crash或者内存泄漏。慎用!

  • - cancel会取消当前的线程工作。但是需要注意的是cancel有可能并不会马上迫使当前的线程停下来,而是在当前线程的工作完成以后,线程才会停下来。如果线程工作已经完成的话,调用cancel并不会有任何作用。在一个操作队列中,如果对某个操作调用了cancel方法,则在该操作执行完毕后,在该操作之后的其他操作将不会执行cancel方法实际上只是将线程标记为cancel状态,并不会停止线程。通常的用法是对线程对象调用方法后,根据线程的状态来判断应该进行的下一步操作。

    e.g.:

    for (int i = 0; i < 100; i++) {
    	if ([NSThread currentThread].isCancelled) 
    		return;
    	NSLog(@"%@ - %d", [NSThread currentThread], i);
        if (i == 50) {
            [[NSThread currentThread] cancel];
        }
    }
    复制代码
线程的执行状态
  • executing表明线程是否正在执行

  • finished表明线程是否执行完毕

  • cancelled表明线程是否被置为取消

与主线程相关的属性
  • isMainThread. NSThread有两个isMainThread的只读属性,其中一个是类型属性,表明当前的线程是否为主线程;另一个为实例属性,表明接收者是否为主线程。即
[NSThread isMainThread];
		
// or
[self.thread isMainThread];
复制代码
  • mainThread类属性,用于获取主线程对象
运行环境查询
  • + isMultiThread表明app是否为多线程应用。假如通过detachNewThreadSelector:toTarget:withObject:为主线程分配了新的工作线程或者是调用了某个线程的start方法的话,则会返回YES。但是如果调用了如POSIX或者多进程服务等非Cocoa的API来分配线程的话,虽然也是有新的线程在跑,但是仍然会返回NO

  • currentThread获取当前所在的线程

  • callStackReturnAddresses获取调用栈的内存地址

  • callStackSymbols获取调用栈的标志位

后两个属性会比较常用于分析crash log

线程属性相关
  • threadDictionary 线程对象的属性集合,可以用来存储与线程相关的数据。

e.g.:

[self.thread.threadDictionary setValue:@"value1" forKey:@"key1"];
复制代码
  • name 线程名称

  • stackSize 线程栈区的size。以byte为单位,且大小必须为4KB的倍数。需要更改此值的话,必须在线程开始之前进行修改。

线程优先级
  • + threadPriority 当前线程的优先级。范围为0.0 ~ 1.0(低到高)。一般是0.5,但是具体的优先级由内核所决定

  • threadPriority 线程优先级。详细查看threadPriority的话,会发现文档中标示即将废弃掉这个属性,使用iOS 8之后出现的qualityOfService来替代标示优先级。

    qualityOfService有5个枚举值:

    typedef NS_ENUM(NSInteger, NSQualityOfService) {
    /* 最高优先级,用于用户的UI交互和屏幕内容绘制 */
    NSQualityOfServiceUserInteractive = 0x21,
    
    /* 用于执行一些需要立即获取结果的任务 */
    NSQualityOfServiceUserInitiated = 0x19,
    
    /* 用于执行并不需要立即返回的任务 */
    NSQualityOfServiceUtility = 0x11,
    
    /* 后台优先级用于处理完全不急的任务 */
    NSQualityOfServiceBackground = 0x09,
    
    /* 默认优先级 */
    NSQualityOfServiceDefault = -1
    复制代码

} ```

  • + setThreadPriority: 设置当前线程的优先级

NSOperation

相比NSThread来说,NSOperation虽然是个抽象类,但是我们可以更加注重事件的逻辑实现,而不用过多关注线程的周期。因为NSOperation是个抽象类,我们需要继承并实现相关的方法来实现自己的类,如果嫌麻烦的话,也可以使用系统提供的NSInvocationOperation或者是NSBlockOperation. 跟NSThread对象一样,调用start方法的话,NSOperation对象就会开始执行自己的任务,但是对于NSOperation对象来说,手动执行操作会加重负担,因为它将一个静默状态的操作绕过了ready状态直接就开始执行,会有较多的消耗。So,一般情况下,都会和NSOperationQueue配合使用。

特点
  • 线程依赖

    比方说现在有个需求是从网络上download文件,但是在download之前需要先登录,也就是说下载的操作依赖于登录,需要在登录之后才能进行download。线程依赖就是类似如此,队列中的操作以指定好的顺序进行执行。指定的操作只能在其依赖的操作都执行完毕后,才会进入到ready状态中。

  • KVO属性观察

  • 线程安全

    NSThread所不同的是,我们可以安全地在多个线程调用NSOperation的方法,不用添加为对象的访问器添加额外的锁。在实现自己的NSOperation类时,需要保证所重写的方法的线程安全性。

  • 异步 vs 同步

    如果是手动来执行操作对象的话,就能够自定义操作的执行方式 -- 异步或者是同步。NSOperation的默认的执行方式是同步执行。同步操作的话,不会在当前任务的线程上创建另一条线程来执行,一旦调用了start方法,就会立马在当前线程上执行任务

    异步方式的话,调用start方法后会立马返回,不管其对应的任务是否已经执行完成。因为异步的operation对象只负责将任务添加到另外的线程中。Operation可以通过直接启动新线程,调用异步方法或者提交block给执行队列来执行任务。

** NSOperation相对于GCD来说,胜在线程依赖和线程的执行状态的管理 **

NSInvocationOperation、NSBlockOperation和自定义Operation

NSInvocationOperationNSBlockOperationNSOperation这个抽象类的实现,前者使用方法作为参数,后者使用block。对这两者来说,如果手动调用start方法的话,都为同步执行任务:

NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[operation1 start];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
	NSLog(@"Operation 2: %@ -- 2", [NSThread currentThread]);
}];
[operation2 start];
复制代码

不过,当NSBlockOperation的任务数多余1的时候,会变为异步执行:

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
	NSLog(@"Operation 2: %@ -- 2", [NSThread currentThread]);
}];
[operation2 addExecutionBlock:^{
	NSLog(@"Operation 2: %@ -- 3", [NSThread currentThread]);
}];
[operation2 start];
复制代码

运行上面这段代码,你会发现operation2的两个block在不同的线程上执行,并不会阻塞线程。所以是为异步执行。

如果系统提供的这两种类型的对象都满足不了需求的话,就需要自己继承NSOperation来实现了。其中实现的方式分为非并发和并发两种类型(Apple的官网上是说nonconcurrent和concurrent,但是在属性中concurrent却建议用asynchronous替换。)

  • 非并发操作

    非并发操作的话简单点,一般情况下只需实现main和初始化方法即可。

    // CustomNonconcurrentOperation.h
    @interface CustomNonconcurrentOperation : NSOperation
    
    @property (nonatomic, assign) NSInteger num;
    
    - (instancetype)initWithNum:(NSInteger)num;
    
    @end
    
    // CustomNonconcurrentOperation.m
    @implementation CustomNonconcurrentOperation
    
    - (instancetype)initWithNum:(NSInteger)num {
    	self = [super init];
    	if (self) {
        	_num = num;
    	}
    	return self;
    }
    
    - (void)main {
    	@try {
        	BOOL isDone = NO;
    
        	while (!self.isCancelled && !isDone) {
            	NSLog(@"%d -- %@", self.num--, [NSThread currentThread]);
            	if (self.num == 0) {
                	isDone = YES;
            	}
        	}
    	} @catch (NSException *exception) {
        NSLog(@"%@", exception.description);
    	} 
    }
    
    @end
    复制代码
  • 并发操作

    需要重写的方法有:

    方法
    start必须重写。不能在里面调用super
    main可选。用于实现任务的执行。建议把任务的实现放在main方法中,而不是start中
    isExecuting isFinished必须重写。维持相关的属性,以便其他对象能够监听相关的属性,并能够生成相对应的KVO通知
    isConcurrent必须重写,返回结果为YES
    // CustomConcurrentOperation.h
    @interface CustomConcurrentOperation : NSOperation {
    	BOOL _executing;
    	BOOL _finished;
    }
    
    @property (nonatomic, assign) NSInteger num;
    
    - (instancetype)initWithNum:(NSInteger)num;
    
    - (void)completeOperation;
    
    @end
    
    //CustomConcurrentOperation.m
    @implementation CustomConcurrentOperation
    
    - (instancetype)initWithNum:(NSInteger)num {
    	self = [super init];
    	if (self) {
        	self.num = num;
        	_executing = NO;
        	_finished = NO;
    	}
    	return self;
    }
    
    - (BOOL)isConcurrent {
    	return YES;
    }
    
    - (BOOL)isExecuting {
    	return _executing;
    }
    
    - (BOOL)isFinished {
    	return _finished;
    }
    
    - (void)start {
    	// 在开启之前都需要检查状态
    	if (self.isCancelled) {
        	[self willChangeValueForKey:@"isFinished"];
        	_finished = YES;
        	[self didChangeValueForKey:@"isFinished"];
        	return;
    	}
    
    	[self willChangeValueForKey:@"isExcuting"];
    	[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    	_executing = YES;
    	[self didChangeValueForKey:@"isExcuting"];
    }
    
    - (void)main {
    	@try {
        	while (!self.isCancelled && !self.isFinished) {
            	NSLog(@"%d -- %@", self.num--, [NSThread currentThread]);
            	if (self.num == 0) {
                	[self completeOperation];
            	}
        	}
    	} @catch (NSException *exception) {
        	NSLog(@"%@", exception.description);
    	}
    }
    
    - (void)completeOperation {
    	[self willChangeValueForKey:@"isFinished"];
    	[self willChangeValueForKey:@"isExecuting"];
    
    	_executing = NO;
    	_finished = YES;
    
    	[self didChangeValueForKey:@"isExecuting"];
    	[self didChangeValueForKey:@"isFinished"];
    }
    
    @end
    复制代码
NSOperationQueue

NSOperationQueue如名称一样,就是一个队列。和NSOperation一起使用。经常用到的NSOperationQueue的属性有个maxConcurrentOperationCount。该属性用于设置队列的最大并发操作数。一开始我以为将maxConcurrentOperationCount设置为1,队列就变成了一个串行队列。但是实际上,NSOperationQueue就算将并发操作数改为1,仍然是一个并行队列。根据Apple的文档:

Operation queues usually provide the threads used to run their operations. Operation queues use the libdispatch library (also known as Grand Central Dispatch) to initiate the execution of their operations. As a result, operations are always executed on a separate thread, regardless of whether they are designated as asynchronous or synchronous operations.

队列中的操作都是在独立的线程中进行操作。在Stack Overflow上也有不少人认为并发操作数为1时,就是一个串行队列。不过我的观点是,此时可能执行方式上与串行队列相似,但是并不能保证就是一个串行队列,所以也有观点认为在NSOperationQueue中如果要实现串行队列的执行方式的话,为每个operation添加线程依赖的话会更好。

虽然NSOperationQueue是一个并发队列,但是我们可以通过修改操作对象的优先级。优先级的属性有下面五个值。比方说operation.queuePriority = NSOperationQueuePriorityHigh;

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
	NSOperationQueuePriorityVeryLow = -8L,
	NSOperationQueuePriorityLow = -4L,
	NSOperationQueuePriorityNormal = 0,
	NSOperationQueuePriorityHigh = 4,
	NSOperationQueuePriorityVeryHigh = 8
};
复制代码

GCD(Grand Central Dispatch)

最后一种常用的线程方法,就是GCD。作为抽象度最高的技术,使用起来就是简单和方便。集合了能够在系统层面提升应用的执行效率,优化体验等优点于一身,是Apple推荐使用的多线程技术(毕竟是亲儿子)。

GCD以block为基本操作单元,是一个FIFO队列,称为dispatch queue。

不过简单是简单,比NSOperation的话,就输在不能取消操作等缺点上。所以先下结论:如果对线程的操作没什么特别要求的话,就简单用GCD就好了,如果对线程的操作要求比较高,各种状态监听,操作取消的话,就用NSOperation

GCD的各个函数中,末尾带_f的,比方说dispatch_sync_fdispatch_async_f都是需要传入系统定义的函数。所以下面的笔记中先忽略带_f的,因为跟不带的差不多。。

队列类型
  1. 主队列(Serial dispatch queue): 通过dispatch_get_main_queue()获取程序主队列。该队列由系统创建并与主线程联系在一起。通过dispatch_main(), UIApplicationMain或者是主线程的CFRunLoopRef来调用提交给主队列的block

  2. 并行队列:通过dispatch_get_global_queue(identifier, flags)获取的队列。其中需要传入两个参数,第一个为队列的优先级,第二个为保留参数,传入0即可。

    优先级的参数有下面几种:

    • QOS_CLASS_USER_INTERACTIVE: 表示任务需要立即被执行。通常用于响应用户输入和UI更新
    • QOS_CLASS_USER_INITIATED: 表示UI发起的异步执行。通常用于需要即时结果同时又能继续响应交互的场景
    • QOS_CLASS_UTILITY: 用于长时间执行的任务,比方说网络数据加载,大数据计算等
    • QOS_CLASS_BACKGROUND: 在后台执行的任务,不会有用户感知
    • QOS_CLASS_DEFAULT: 默认优先级
    • QOS_CLASS_UNSPECIFIED: 未指定
  3. 串行队列:一般为用户通过dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)所创建的队列。其中第一个参数为队列名,第二个参数表明队列的类型,当attr为NULL或者是DISPATCH_QUEUE_SERIAL时才是串行队列,为DISPATCH_QUEUE_CONCURRENT时是并行队列。

** 对主队列和全局并行队列发送dispatch_suspend, dispatch_resumedispatch_set_context的话并不会有任何效果**

到了这里,就得讲讲队列的同步和异步操作,先下demo:

  • 串行队列,同步执行:

    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
    	NSLog(@"Block 1: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 2: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 3: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 4: %@", [NSThread currentThread]);
    });
    复制代码

    输出结果:

    2017-02-21 22:52:44.896 Thread[37399:1718001] Block 1: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.897 Thread[37399:1718001] Block 2: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.897 Thread[37399:1718001] Block 3: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.898 Thread[37399:1718001] Block 4: <NSThread: 0x6000000667c0>{number = 1, name = main}
    复制代码
  • 串行队列,异步执行:

    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
    	NSLog(@"Block 1: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 2: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 3: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 4: %@", [NSThread currentThread]);
    });
    复制代码

    输出结果:

    2017-02-21 22:57:31.561 Thread[37508:1722281] Block 1: <NSThread: 0x608000264e00>{number = 3, name = (null)}
    2017-02-21 22:57:31.562 Thread[37508:1722281] Block 2: <NSThread: 0x608000264e00>{number = 3, name = (null)}
    2017-02-21 22:57:31.562 Thread[37508:1722281] Block 3: <NSThread: 0x608000264e00>{number = 3, name = (null)}
    2017-02-21 22:57:31.562 Thread[37508:1722281] Block 4: <NSThread: 0x608000264e00>{number = 3, name = (null)}
    复制代码
  • 并行队列,同步执行

    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
    	NSLog(@"Block 1: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 2: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 3: %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
    	NSLog(@"Block 4: %@", [NSThread currentThread]);
    });
    复制代码

    输出结果:

    2017-02-21 22:52:44.896 Thread[37399:1718001] Block 1: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.897 Thread[37399:1718001] Block 2: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.897 Thread[37399:1718001] Block 3: <NSThread: 0x6000000667c0>{number = 1, name = main}
    2017-02-21 22:52:44.898 Thread[37399:1718001] Block 4: <NSThread: 0x6000000667c0>{number = 1, name = main}
    复制代码
  • 并行队列,异步执行

    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
    	NSLog(@"Block 1: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 2: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 3: %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
    	NSLog(@"Block 4: %@", [NSThread currentThread]);
    });
    复制代码

    输出结果:

    2017-02-21 23:02:29.373 Thread[37623:1726500] Block 2: <NSThread: 0x60000006e780>{number = 4, name = (null)}
    2017-02-21 23:02:29.373 Thread[37623:1726501] Block 1: <NSThread: 0x60000006e700>{number = 3, name = (null)}
    2017-02-21 23:02:29.373 Thread[37623:1726503] Block 3: <NSThread: 0x60000006e900>{number = 5, name = (null)}
    2017-02-21 23:02:29.373 Thread[37623:1726528] Block 4: <NSThread: 0x60000006e980>{number = 6, name = (null)}
    复制代码

从上面的代码和运行结果可以看出,队列的类型决定了是否会新开线程执行给定的任务,执行的方式决定了执行的顺序

队列类型\执行方式同步(Synchronous)异步(Asynchronous)
串行(Serial)不会新开线程,block按添加的顺序执行创建新的线程,block按添加的顺序执行(至少现在看来一个dispatch_queue_t对象就会建一个线程)
并行(Concurrent)不会新建线程,block按添加的顺序执行根据block的数量创建线程,block的执行顺序不确定

至少现在看来,串行并行决定了block的执行顺序(注意并行同步),同步和异步决定了线程的创建与否

GCD Qos

GCD在后面推出了Qos这个类用来替代原有的优先级队列:

Global queueQos Class
Main threadUser-interactive
DISPATCH_QUEUE_PRIORITY_HIGHUser-initated
DISPATCH_QUEUE_PRIORITY_DEFAULTDefault
DISPATCH_QUEUE_PRIORITY_LOWUtility
DISPATCH_QUEUE_PRIORITY_BACKGROUNDBackground
dispatch_set_target_queue

dispatch_set_target_queue有两个作用:

  • 设置优先级

    使用dispatch_queue_create创建的队列,如果没指定优先级别的话,默认的优先级是QOS_CLASS_DEFAULT。下面的代码是指定了优先级的例子:

    dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0);
    dispatch_queue_t queue = dispatch_queue_create("com", attr);
    复制代码

    我们可以通过dispatch_set_target_queue来设置优先级:

    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
    
    
    dispatch_set_target_queue(queue, backgroundQueue);
    // 此时queue的优先级就跟backgroundQueue一致
    // 也可以像下面这样
    // dispatch_set_target_queue(queue, DISPATCH_PRIORITY_BACKGROUND);
    复制代码
  • 设置队列层次

    在实际程序中,有可能一个任务会被分割成多个小任务进行执行,但是我们仍需同步执行的话,也可以使用dispatch_set_target_queue来进行设置:创建一个串行队列,然后将所有任务队列的参考设为该串行队列:

    dispatch_queue_t serialQueue = dispatch_queue_create("com.thread.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_SERIAL);
    
    dispatch_set_target_queue(queue, serialQueue);
    dispatch_set_target_queue(queue2, serialQueue);
    复制代码

    输出结果:

    2017-02-22 12:29:21.450 Thread[40387:1879701] Queue 1 Block 1: <NSThread: 0x60800006fa40>{number = 3, name = (null)}
    2017-02-22 12:29:21.450 Thread[40387:1879701] Queue 1 Block 2: <NSThread: 0x60800006fa40>{number = 3, name = (null)}
    2017-02-22 12:29:21.451 Thread[40387:1879701] Queue 2 Block 3: <NSThread: 0x60800006fa40>{number = 3, name = (null)}
    2017-02-22 12:29:21.451 Thread[40387:1879701] Queue 2 Block 4: <NSThread: 0x60800006fa40>{number = 3, name = (null)}
    复制代码
dispatch_after

这个函数常用于延迟执行。比方说,我们常用的延迟执行perfromSelector:withObject:afterDelay:

[self performSelector:@selector(test) withObject:nil afterDelay:1];
复制代码

如果使用GCD的话,则是:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	[self test];
});
复制代码

这两者执行起来是一样的。

dispatch_after会在指定的时间之后,异步将block添加到指定的队列中执行

dispatch_once

dispatch_once这个函数我们通常是用于创建单例,因为它的block参数只会在app的声明周期里面执行一次。如果同时被多个线程所调用的话,可能会引起线程阻塞

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
	// code to be executed once
});
复制代码
信号量(Semaphore)

GCD中信号量一般是用来控制线程同步的操作,也有用来加锁解锁的操作,举个例子:

dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
	NSLog(@"Hello");
	dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"World");
复制代码

上面的这段代码,会打印出"Hello"和"World",但是如果注释掉dispatch_semaphore_signal(semaphore)的话,你就会发现只打印了"Hello"。因为初始化的信号量为0,调用一次dispatch_semaphore_signal()方法的话,会使信号量+1,当信号量>0的话,dispatch_semaphore_wait()方法后面的代码才会开始执行。就是说信号量=0时,原来的线程会被阻塞,直到“收到”signal,才会继续执行。

dispatch_barrier

dispatch_barrier常用于并行队列中。在并行队列中,可能会出现某个操作我们希望能进行串行执行来避免资源竞争等问题的话,那么dispatch_barrier就能派上用场了。其保证了在这个并行队列中,同一时刻只能有一个block在执行,访问资源。

dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
	NSLog(@"1 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
	NSLog(@"2 %@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
	NSLog(@"Barrier %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
	NSLog(@"3 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
	NSLog(@"4 %@", [NSThread currentThread]);
});
复制代码

输出结果:

2017-02-22 15:16:33.818 Thread[42174:1964999] 2 <NSThread: 0x60800006f9c0>{number = 4, name = (null)}
2017-02-22 15:16:33.819 Thread[42174:1964997] 1 <NSThread: 0x60800006f980>{number = 3, name = (null)}
2017-02-22 15:16:33.822 Thread[42174:1964997] Barrier <NSThread: 0x60800006f980>{number = 3, name = (null)}
2017-02-22 15:16:33.823 Thread[42174:1964997] 3 <NSThread: 0x60800006f980>{number = 3, name = (null)}
2017-02-22 15:16:33.823 Thread[42174:1964999] 4 <NSThread: 0x60800006f9c0>{number = 4, name = (null)}
复制代码

总结

啰啰嗦嗦写了一堆iOS多线程相关的知识点,希望自己能够活学活用吧。不会说学了就忘了。

请大家指正。


参考链接

  1. 深入理解Runloop
  2. Which is the best of GCD, NSThread or NSOperationQueue?
  3. Concurrenty Programming Guide
  4. 细说GCD(Grand Central Dispatch)如何用
  5. Prioritize Work with Quality of Service Classes

转载于:https://juejin.im/post/5a30e2396fb9a0450310140b

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值