- 程序:每一个应用程序App都称为一个程序。
- 进程:正在运行的一个应用程序就是一个进程,相当于一个任务,进程拥有全部的资源,负责资源的调度和分配。
- 线程:线程就是程序中一个单独的代码块(单独的功能)。
- 主线程:每个正在运行的程(即进程)序,至少包含一个线程,这个线程就叫做主线程。
- 子线程:iOS允许用户自己开辟新的线程,相对于主线程而言,这些新的线程都可以称为子线程。
- 注意:子线程与主线程是独立的运行单元,各自执行互不影响,可以并发执行。
- 验证当前线程是否是主线程
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"%d",isResult);
//结果为1,为主线程
- 为了防止线程同步,即两条线程同时执行同一个任务,处理同一条数据,例如买票程序;此时就需要对线程加解锁,线程开始加锁,结束时解锁。
// 加锁,避免线程同步
NSLock *oneLock = [[NSLock alloc]init];//线程锁对象
[oneLock lock]; // 加锁
[oneLock unlock]; // 解锁
- 线程都是自带
runLoop
,主线程的默认打开的,子线程默认是关闭的,因此记得打开子线程runLoop
。
PThread
- C语言版的简单开辟子线程
代码示例:
#import <pthread.h> // 导入此框架,验证PThread
// 开辟子线程的方式:
- (void)creatThreadWithPThread{
pthread_t thread1;
NSString *identification = @"threadOne"; // 线程标识符
// (__bridge void *)(identification) 将OC的id类型转化为 void* 类型
pthread_create(&thread1, NULL, threadFunc, (__bridge void *)(identification));
// 参数一:线程变量;参数二:线程属性,这里没有进行设定;参数三:子线程将要执行的方法;参数四:执行方法传递的参数,这里传递的是线程标识符,
// thread1 pthread_t 0x70000ad70000 0x000070000ad70000
}
// 线程将要执行的方法
void *threadFunc(void *paragram){
BOOL isResult = [[NSThread currentThread]isMainThread];
// (__bridge NSString *)(paragram) 将void*类型转化为OC的id类型
NSLog(@"传递的标识符为:%@",(__bridge NSString *)(paragram));
NSLog(@"当前线程为:%@",isResult?@"主线程":@"子线程");
pthread_t thisThrad = pthread_self();
// thisThrad pthread_t 0x70000ad70000 0x000070000ad70000
pthread_cancel(thisThrad);
NSLog(@"-这句话执行不到-");
return NULL;
}
// 经过断点测试,pthread_self()获取的是执行该方法的子线程。
NSThread
NSThread
是对PThread
进行OC层次的封装。NSThread
创建子线程方式一:使用类方法进行创建。NSThread
创建子线程方式二:使用对象方法进行创建。
使用NSThread
的第一种方式,使用类方法创建
- 在ios10.0之后,类方法创建存在block方法创建。
- 代码示例如下:
// NSThreadAndNSObjectViewController.m
// NSThread创建子线程方式一:使用类方法进行创建
- (void)creatThreadWithNSThreadFirst{
NSDictionary *dict = @{@"key":@"NSThreadFirst"};
[NSThread detachNewThreadSelector:@selector(NSThreadFirstFunc:) toTarget:self withObject:dict];
// ios10.0 之后可以利用block的模式添加子线程
[NSThread detachNewThreadWithBlock:^{
BOOL isResult = [NSThread isMultiThreaded];
NSLog(@"类方式创建-block-当前线程为:%@",isResult?@"子线程":@"主线程");
}];
}
- (void)NSThreadFirstFunc:(id)argument{
if ([argument isKindOfClass:[NSDictionary class]]) {
NSLog(@"%@",[argument valueForKey:@"key"]);
}
BOOL isResult = [NSThread isMultiThreaded]; //线程是否是子线程
NSLog(@"类方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
}
使用NSThread
的第二种方式
- 记得手动启动线程,利用
NSThread
对象方法创建的子线程需要。 - 手动创建的子线程不会自动添加释放池,因此需要我们自己手动添加自动释放池。
- 在ios10.0之后,对象创建子线程也有block模式。
// 自动释放池
@autoreleasepool {}
threadPriority
线程优先级,值范围0.0~1.0,默认0.5。- 代码示例:
// NSThreadAndNSObjectViewController.m
// NSThread创建子线程方式二:使用对象方法进行创建
- (void)creatThreadWithNSThreadSecond{
NSDictionary *dict = @{@"key":@"NSThreadSecond"};
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(NSThreadSecond:) object:dict];
// ios10.0 之后,可以利用block进行子线程的创建
NSThread *thread2 = [[NSThread alloc]initWithBlock:^{
BOOL isResult = [NSThread isMultiThreaded];
[NSThread exit]; // 线程强制终止,写在那个子线程方法内,终止哪个子线程
NSLog(@"对象方法创建-block-当前线程为:%@",isResult?@"子线程":@"主线程");
}];
// 线程优先级,值范围0.0~1.0,默认0.5
thread1.threadPriority = 0.2;
thread2.threadPriority = 1.0;
// 对象方式创建子线程,需手动开启线程。
[thread1 start];
[thread2 start];
self.thread = thread1;
}
- (void)NSThreadSecond:(id)argument{
// 手动开辟的子线程是不会自动添加释放池的,需要手动添加自动释放池
@autoreleasepool {
if ([argument isKindOfClass:[NSDictionary class]]) {
NSLog(@"%@",[argument valueForKey:@"key"]);
}
// 线程取消
for (int i = 0; i<2000; i++) {
NSLog(@"%d",i);
if (self.thread.cancelled) {
break;
}
if (i == 200) {
[self.thread cancel];
}
}
BOOL isResult = [NSThread isMultiThreaded];
NSLog(@"对象方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
}
}
- 线程的手动终止
- (void)cancel;
,取消不了正在进行的任务(因此一般应用于NSOperationQueue
用来结束没有来得急执行的任务),thread
的状态就变成了cancelled
,结合cancelled
来终止任务。 - 强制终止任务
exit
用法,在哪个线程中执行,就是终止哪个线程。
[NSThread exit];
NSObject
- 因为所有的类都继承自根类,因此创建线程时,直接用
self
调用对应的方法即可。 - 在应用程序打开的时候,系统会自动为主线程操作的方法中创建一个自动释放池;但是我们手动开辟的子线程是不会自动添加释放池的,我们需要手动添加自动释放池。
- 利用子线程处理数据之后,对于界面的刷新以及UI控件的添加显示都在主线程中操作。
- 代码示例:
// NSThreadAndNSObjectViewController.m
#pragma mark - NSObject
// NSObject创建子线程方式
- (void)creatThreadWithNSObject{
[self performSelectorInBackground:@selector(NSObjectThreadFunc) withObject:nil];
}
- (void)NSObjectThreadFunc{
// 当程序打开运行时,系统会自动为主线程添加自动释放池,对于子线程需要我们自己添加。
@autoreleasepool {
BOOL isResult = [NSThread isMultiThreaded];
NSLog(@"NSObject方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
// 利用子线程处理数据之后,对于界面的刷新以及UI控件的添加显示都在主线程中操作
[self performSelectorOnMainThread:@selector(NSObjectMainThread) withObject:nil waitUntilDone:YES];
}
}
- (void)NSObjectMainThread{
}
定时器的使用
- 在子线程中使用定时器,需要手动启动执行定时器任务。
- 在子线程当中使用定时器,需要手动开启执行定时器任务,也就是需要手动开启事件循环,定时器生效—执行任务
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(startTimerTask) userInfo:nil repeats:YES];
// 在子线程当中使用定时器,需要手动开启执行定时器任务,也就是需要手动开启事件循环,定时器生效---执行任务
[[NSRunLoop currentRunLoop] run];
// 在ARC环境下重置timer,注意添加retain
NSOperationQueue
- 创建任务队列需要以下几步:
- 1、创建子线程任务(存在两种方式)
- 2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
- 3、创建任务队列
- 4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
- 5、将子线程任务添加到任务队列中
- 注意:第二步必须在第一步后面,第四步必须在第三步后面,第五步必须在最后。
- 线程任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行
- 在任务队列中放的都是
NSOperation
子类的对象(NSBlockOperation、NSInvocationOperation
)。 NSOperation
是对GCD进行OC层次的封装,NSOperationQueue
功能更多,封装进去KVO,可以通过KVO对每个任务进行观察,是否完成,是否取消。
使用NSOperationQueue
方式创建子线程
创建任务、设置任务间的依赖关系
- 创建任务存在两种方式(
NSBlockOperation、NSInvocationOperation
),示例代码如下:
// 1、创建子线程任务(存在两种方式)
// 方式1:NSBlockOperation
NSBlockOperation *blockOperationOne = [NSBlockOperation blockOperationWithBlock:^{
// 子线程将要执行的方法
NSLog(@">>blockOperationOne");
}];
NSBlockOperation *blockOperationTwo = [NSBlockOperation blockOperationWithBlock:^{
// 子线程将要执行的方法
NSLog(@">>blockOperationTwo");
}];
// 方式2:NSInvocationOperation
NSInvocationOperation *invocationOperationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationOneFunc) object:nil];
NSInvocationOperation *invocationOperationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationTwoFunc) object:nil];
// 注意:任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行
- 设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖),示例代码如下:
// 2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
[invocationOperationOne addDependency:invocationOperationTwo];
// One 依赖于 Two,Two执行完成之后,执行One
创建任务队列、设置任务队列最大并发数
- 最大并发数设置为1,代表不设置。
- 示例代码如下:
// 3、创建任务队列
NSOperationQueue *quere = [[NSOperationQueue alloc]init];
// 4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
[quere setMaxConcurrentOperationCount:2];
// 设置为n,代表n个任务)同时执行(同时执行的任务之间不存在依赖关系),设置为1,则会按照添加顺序,顺序执行
将任务添加到任务队列中
// 5、将子线程任务添加到任务队列中
[quere addOperation:invocationOperationOne];
[quere addOperation:invocationOperationTwo];
[quere addOperation:blockOperationOne];
[quere addOperation:blockOperationTwo];
// 如果任务之间没有依赖关系,且最大并发数设置为1,则将会按照任务添加到队列中的顺序执行
- 如果设置最大并发数为1,而各个任务之间不存在任何依赖关系,此时任务将会按照添加到队列中的先后顺序执行。
- 如果设值最大并发数为n,则会每次同时执行n个队列中的任务,按照任务添加的先后顺序,即同时执行最前面的未被执行的两个任务。
- 如果第一个任务完成之后而第二任务未完成,则同时执行第二个和第三个任务。
- 假设存在任务1、任务2、任务3,任务2依赖于任务1,且依次添加到队列中;如果最大并发数为2,则同步执行任务1和任务3,一旦任务1执行完毕,无论任务3是否结束,都将开始执行任务2;如果最大并发数为1,则按照任务1、任务3、任务2的顺序执行。因为任务2依赖于任务1,执行的过程中会先执行任务2之后的下一个任务,相当于将任务2与其下一个任务进行换位执行。
全部代码示例、及运行结果截图
- 代码示例:
// NSOperationQueueViewController.m
#pragma mark - NSOperationQueue
- (void)creatThreadWithNSOperationQuere{
// 1、创建子线程任务(存在两种方式)
// 方式1:NSBlockOperation
NSBlockOperation *blockOperationOne = [NSBlockOperation blockOperationWithBlock:^{
// 子线程将要执行的方法
NSLog(@">>blockOperationOne");
}];
NSBlockOperation *blockOperationTwo = [NSBlockOperation blockOperationWithBlock:^{
// 子线程将要执行的方法
NSLog(@">>blockOperationTwo");
}];
// 方式2:NSInvocationOperation
NSInvocationOperation *invocationOperationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationOneFunc) object:nil];
NSInvocationOperation *invocationOperationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationTwoFunc) object:nil];
// 注意:任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行
// 2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
[invocationOperationOne addDependency:invocationOperationTwo];
// One 依赖于 Two,Two执行完成之后,执行One
// 3、创建任务队列
NSOperationQueue *quere = [[NSOperationQueue alloc]init];
// 4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
[quere setMaxConcurrentOperationCount:2];
// 设置为2,代表两个任务同时执行(同时执行的任务之间不存在依赖关系),设置为1,则会按照添加顺序,顺序执行
// 5、将子线程任务添加到任务队列中
[quere addOperation:invocationOperationOne];
[quere addOperation:invocationOperationTwo];
[quere addOperation:blockOperationOne];
[quere addOperation:blockOperationTwo];
// 如果任务之间没有依赖关系,且最大并发数设置为1,则将会按照任务添加到队列中的顺序执行
}
- (void)invocationOperationOneFunc{
NSLog(@">>子线程:invocationOperationOne调用的方法:%s",__func__);
}
- (void)invocationOperationTwoFunc{
[NSThread sleepForTimeInterval:10]; // 线程10秒后执行
NSLog(@">>子线程:invocationOperationTwo调用的方法:%s",__func__);
}
- 运行结果:
- 1、最大并发数为1,invocationOperationTwo任务10秒后执行,One 依赖于 Two
- 2、最大并发数为2,invocationOperationTwo任务10秒后执行,One 依赖于 Two
- 1、最大并发数为1,invocationOperationTwo任务10秒后执行,One 依赖于 Two
GCD(Grand Central Dispatch)
- 无论是方法还是其它,均以
dispatch_
开头,与其有关的枚举均以DISPATCH_QUEUE_
开头 - 是苹果主推的一种多线程计数,Apple自己封装,是一套纯C的代码,都是C语言的API,抽象程度最高,执行效率最高(1.C语言 2.充分利用设备多核的特性)
- 使用步骤:
- 创建任务队列(串行或者并行)
- 添加任务(同步或异步)
- MRC下需要释放任务队列
创建串行队列
- 串行队列标识:
DISPATCH_QUEUE_SERIAL
- 只有在上一个任务结束之后,才会执行下一个任务
- 代码示例:
#pragma mark - 串行队列
- (void)creatSerialQuere{
// 1.串行队列有两种获取方式
// 1.1串行队列获取方式一(系统创建):
// 使用系统创建好的串行队列,往队列中添加的任务是在主线程中执行的;
dispatch_queue_t queueSerialFirst = dispatch_get_main_queue();
// 1.2串行队列获取方式二(手动创建):
// 参数一:队列的唯一标识,一般以反域名的方式来标识;参数二:设置队列方式,即串行还是并行,DISPATCH_QUEUE_SERIAL 代表串行队列
dispatch_queue_t queueSerialSecond = dispatch_queue_create("com.wangsk.second", DISPATCH_QUEUE_SERIAL);
// 2. 添加任务
// 2.1 添加异步任务
dispatch_async(queueSerialSecond, ^{
NSLog(@"2.1串行队列中,添加异步任务,任务内容");
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"2.1当前线程为:%@",isResult?@"主线程":@"子线程");
});
// 2.2 添加同步任务
dispatch_sync(queueSerialSecond, ^{
NSLog(@"2.2串行队列中,添加同步任务,任务内容");
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"2.2当前线程为:%@",isResult?@"主线程":@"子线程");
});
// 3. 释放队列,MRC模式下需要
// dispatch_release(queueSerialSecond);
}
/*
执行结果:
2017-08-23 20:40:41.627 Multi-threaded[36375:2254778] 2.1串行队列中,添加异步任务,任务内容
2017-08-23 20:40:41.627 Multi-threaded[36375:2254778] 2.1当前线程为:子线程
2017-08-23 20:40:41.627 Multi-threaded[36375:2254744] 2.2串行队列中,添加同步任务,任务内容
2017-08-23 20:40:41.628 Multi-threaded[36375:2254744] 2.2当前线程为:主线程
*/
创建并行队列
- 并行队列标识:
DISPATCH_QUEUE_CONCURRENT
- 所有的任务并发执行,也就是并行队列中的任务同时执行
- 代码示例:
#pragma mark - 并行队列
- (void)creatConcurrentQuere{
// 1. 并行队列有两种获取方式
// 1.1 并行队列获取方式一(系统创建):
// 参数一:队列优先级;参数二:预留参数
dispatch_queue_t concurrentQuereFirst = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 1.2 并行队列获取方式二(手动创建):
dispatch_queue_t concurrentQuereSecond = dispatch_queue_create("com.wangsk.concurrent", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加任务
// 2.1添加异步任务
dispatch_async(concurrentQuereSecond, ^{
NSLog(@"2.1并行队列,添加异步任务,任务内容");
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"2.1当前线程为:%@",isResult?@"主线程":@"子线程");
//如果执行完子线程任务之后,回到主线程去执行任务 异步任务
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2.1返回主线程,执行UI界面刷新等操作");
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"2.1-当前线程为:%@",isResult?@"主线程":@"子线程");
});
});
// 2.2添加同步任务
dispatch_sync(concurrentQuereSecond, ^{
NSLog(@"2.2并行队列,添加同步任务,任务内容");
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"2.2当前线程为:%@",isResult?@"主线程":@"子线程");
});
// 3. 释放队列,MRC模式下需要
// dispatch_release(concurrentQuereSecond);
}
/*
执行结果
2017-08-24 10:54:49.617 Multi-threaded[1191:47267] 2.2并行队列,添加同步任务,任务内容
2017-08-24 10:54:49.617 Multi-threaded[1191:47322] 2.1并行队列,添加异步任务,任务内容
2017-08-24 10:54:49.617 Multi-threaded[1191:47267] 2.2当前线程为:主线程
2017-08-24 10:54:49.617 Multi-threaded[1191:47322] 2.1当前线程为:子线程
2017-08-24 10:54:49.624 Multi-threaded[1191:47267] 2.1返回主线程,执行UI界面刷新等操作
2017-08-24 10:54:49.624 Multi-threaded[1191:47267] 2.1-当前线程为:主线程
*/
总结
- 并行:就是队列里面的任务(代码块,block)不是一个个执行,而是并发执行,也就是可以同时执行的意思
- 串行:队列里面的任务一个接着一个执行,要等前一个任务结束,下一个任务才可以执行
- 异步:具有新开线程的能力
同步:不具有新开线程的能力,只能在当前线程执行任务
并行+异步:就是真正的并发,新开有有多个线程处理任务,任务并发执行(不按顺序执行)
- 串行+异步:新开一个线程,任务一个接一个执行,上一个任务处理完毕,下一个任务才可以被执行
- 并行+同步:不新开线程,任务一个接一个执行
串行+同步:不新开线程,任务一个接一个执行
添加同步任务:
- 添加同步任务,到系统创建的串行队列中,会造成线程死锁。
- 添加同步任务,到手动创建的串行队列中,执行任务在主线程。
- 添加同步任务,到系统创建的并行队列中,执行任务在主线程。
- 添加同步任务,到手动创建的并行队列中,执行任务在主线程。
- 添加异步任务:
- 添加异步任务,到系统创建的串行队列中,执行任务在主线程。
- 添加异步任务,到手动创建的串行队列中,执行任务在子线程
- 添加异步任务,到系统创建的并行队列中,执行任务在子线程。
- 添加异步任务,到手动创建的并行队列中,执行任务在子线程。
- 总的来说:
- 添加同步任务,不管在串行还是在并行队列中,所执行的任务都是在主线程中操作。
- 添加同步任务到系统创建的串行队列中,将会造成线程死锁。
- 添加异步任务,如果添加到系统创建的串行队列中,那么,所执行的任务都是在主线程中操作;而将异步任务添加到手动创建的队列中,那么,所执行的任务都是在子线程中操作。因此,一般使用手动创建的串行队列。
线程死锁
- 两个线程相互等待,两个线程都不执行任务(队列遵循FIFO)
- 代码示例:
#pragma mark - 线程死锁
- (void)testThreadLock{
dispatch_queue_t quere = dispatch_get_main_queue();
dispatch_sync(quere, ^{
NSLog(@"输出>>>>");
});
}
- 为了防止线程死锁,避免在系统创建的串行队列中,添加同步任务。
线程安全
加锁
- 互斥锁:
@synchronized (<#token#>) {// 互斥操作 }
,括号(<#token#>)
内是锁对象,设置锁对象时必须保证多个线程访问的都是同一个对象,锁对象必须是唯一的,还必须是id类型;操作尽量要少些,因为代码越多,效率越低。因此,在互斥操作中最好只写对互斥数据的操作。 - 递归锁:用来解决递归调用,进行加锁。
- 条件锁:可以设置条件来进行加锁或者解锁。
- 分布锁:对于文件系统可以进行访问。
- 自旋锁:原子性(atomic),加的就是自旋锁,比互斥锁的效率高,程序没有进入休眠或者阻塞,相当于在执行一个很大的循环,没有休眠,执行任务,因此说效率要高一点。
信号量机制
- 在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)
- GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。
- 每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。
- 根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。
// 代码示例:
#pragma mark - 线程安全
- (void)threadSafe{
// 如果利用信号量进行线程的加解锁,信号量初始值应当设置为1,这里为了测试,设置的值为3,orig为3
dispatch_semaphore_t semapore_t = dispatch_semaphore_create(3); // value = 3,orig = 3
// 发出等待信号,信号量-1,value值发生改变,value为0,加锁
dispatch_semaphore_wait(semapore_t, DISPATCH_TIME_FOREVER); // value = 2,orig = 3
// 发出信号,信号量+1,value值发生改变,解锁
dispatch_semaphore_signal(semapore_t); // value = 3,orig = 3
}