先了解一下进程和线程吧,面试也经常问到。
- 进程:是系统进行资源分配和调度的一个独立单位,拥有独立的内存空间,
- 线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源。
- 关系:一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
- 区别:进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
- 线程的划分尺度小于进程,使得多线程程序的并发性高。
- 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
- 优缺点: 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
1.iOS中的多线程比较
类型|特点
---|---|---
NSThread |生命周期需要自己管理,不够方便使用,一般用来获取当前线程使用。
NSOperation|父类,抽象类,不能直接使用 基GCD的封装,使用起来面向对象一些。生命周期不用自己管理。可以设置依赖关系控制线程先后执行的顺序
GCD|基于C语言,功能强大。在block回调中执行任务,使用方便。线程执行后不能取消,生命周期不用管理
2. NSThread
- 类方法
//新开线程,在block中执行任务
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//新开线程,在@selector响应方法中执行任务
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
-(void)run
{
NSLog(@"%@",[NSThread currentThread]);
//退出当前线程
[NSThread exit];
}
- 对象方法
//返回创建的线程,在block中执行任务
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//要start才能开始执行任务
[thread start];
//新开线程,在@selector响应方法中执行任务
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[self.thread start];
-(void)newThread
{
NSLog(@"newThread%@",[NSThread currentThread]);
//退出当前线程
[NSThread exit];
}
- 直接self调用
[self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
2. NSOperation
NSOperation :父类,抽象类,不能直接使用。
NSOperationQueue : 队列
NSBlockOperation: 线程,在block回调里面处理任务
NSInvocationOperation : 线程,在@selector方法里面调用任务
-
NSOperationQueuePriority : 线程优先级
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
//创建block回调线程任务
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation:%@",[NSThread currentThread]);
}];
//创建在@selector方法里面调用线程任务
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
//创建队列,然后将线程任务添加到队列中,自动执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置队列的最大并发线程量。默认是NSOperationQueueDefaultMaxConcurrentOperationCount
queue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
//设置依赖关系,invocationOperation依赖blockOperation,即是invocationOperation在blockOperation执行完成之后再执行,如果不设置的话就是并发执行。
//[invocationOperation addDependency:blockOperation];
//添加多条线程到队列中
[queue addOperations:@[blockOperation,invocationOperation] waitUntilFinished:YES];
//单独添加一条线程
// [queue addOperation:invocationOperation];
// [queue addOperation: blockOperation];
-(void)invocationOperation
{
//加线程锁,单一线程资源访问,不能同时多个线程访问。
@synchronized (self) {
NSLog(@"invocationOperation:%@",[NSThread currentThread]);
}
}
3.GCD
类型 | 说明 |
---|---|
同步(sync) | 它会阻塞当前线程(即停止当前的任务)执行新任务执行完毕,然后当前任务才会继续往下运行,都是在当前线程执行,不会另开线程。同步线程中,使用串行队列和并行队列并无区别。 |
异步(async) | 当前线程会直接往下执行,它不会阻塞当前线程。会另开线程,在别的线程执行。 1.如果使用的是串行队列,那么只会创建一条新的线程,然后在新的线程里面依次执行任务。 2.如果是使用的并行队列,那么在每次添加了任务之后都会创建一个新的线程去并行执行,全局并发队列是系统提供的一个并发队列。 |
异步线程组(group_async) | 基础功能和异步(async)一样,但是加了一个group的概念,在group的队列执行完毕之后会有统一的回调通知dispatch_group_notify和dispatch_group_wait。 |
串行队列 | 1.如果是在同步线程中,那么不会开辟创建新线程,在当前线程执行队列中的任务。 2.如果是在异步线程中,则开启一条新线程,在新线程中执行队列中的任务。 |
并发队列 | 1.如果是在同步线程中,那么不会开辟创建新线程,在当前线程执行队列中的任务。(和串行队列没区别)。 2.如果是在异步线程中,则每一个队列每调用一次就会开启一条新线程,在新线程中并发执行队列中的任务。 |
全局并发队列 | 系统提供的一个并发队列。可直接调用,不用创建 |
3.1队列的创建
//@param <#const char * _Nullable label#>指的是队列的名称,传入C语言字符串
//<#dispatch_queue_attr_t _Nullable attr#>指的是队列的类型
//DISPATCH_QUEUE_SERIAL and NULL 串行队列
//DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
串行队列( DISPATCH_QUEUE_SERIAL || NULL)
dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("syncQueue2", NULL);
并行队列 (DISPATCH_QUEUE_CONCURRENT)
dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);
全局队列
- 第一个参数是优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND 在background执行
+第二个参数 flag Reserved for future use 预留的参数,传0就可以了
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
主队列
dispatch_get_main_queue()
3.2同步线程 (sync)
注意同步线程操作传入的队列不能是当前队列,如果是当前队列会造成线程死锁的状态,下面会讲到
//DISPATCH_QUEUE_SERIAL 串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
//创建同步线程,在队列block回调中添加执行任务
dispatch_sync(serialQueue1, ^{
//添加执行的任务
NSLog(@"serialQueue1-->%@",[NSThread currentThread]);
});
3.3异步线程 (async)
**异步线程串行队列和异步线程并发队列的差别主要是: **
**只有一条队列的情况下,如果是串行队列那么只会开启一条新的线程,然后任务依次执行。如果是并发队列的话,那么每添加一次任务都会开启一条新的线程,然后个人线程并发执行
**
- 异步线程串行队列
//DISPATCH_QUEUE_SERIAL 串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
//创建异步线程,开启一条新线程执行任务
dispatch_async(serialQueue1, ^{
//串行队列执行
NSLog(@"serialQueue1-->%@",[NSThread currentThread]);
for (int i = 10; i<20; i++) {
NSLog(@"%d",i);
}
});
- 异步线程并发队列
//DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);
//创建异步线程,在队列block回调中添加执行任务
dispatch_async(concurrent1, ^{
//并行队列执行
NSLog(@"global1-->%@",[NSThread currentThread]);
for (int i = 40; i<50; i++) {
NSLog(@"%d",i);
}
});
//全局队列 flag Reserved for future use 预留的参数,传0就可以了
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建异步线程,在队列block回调中添加执行任务
dispatch_async(global, ^{
NSLog(@"global1-->%@",[NSThread currentThread]);
for (int i = 40; i<50; i++) {
NSLog(@"%d",i);
}
});
- 线程卡死问题
在原来的线程上添加一个同步串行任务,如果添加的串行队列就是当前队列的话就会造成线程卡死,因为原本线程是在该队列中执行的,这里又创建了一个任务在该队列中,原先的任务就会停止,等待这个队列的新任务执行完毕后再执行主队列的任务,但是这里创建的队列又是原先队列,任务是添加该队列中,同一线程中相互等待,造成线程死锁状态。
//用这个是会堵塞死线程,因为原本线程是在主队列中执行的,这里又创建了一个队列主队列的任务就会停止,等待这个队列的任务执行完毕后再执行主队列的任务,但是这里创建的队列又是主队列,任务是添加在主队列中,所有main线程中相互等待,造成线程死锁状态
//主线程
-(void)test
{
NSLog(@"我是主线程");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"在这里我会被卡死,我不会被log出来");
});
NSLog(@"上面已经卡死了,所以我也不会被log出来");
}
3.4Dispatch Groups的使用
- dispatch_group_async 异步队列组。也是一种创建异步线程执行的方法,和dispatch_async一样
- dispatch_group主要是来进行线程同步,例如我们通常一个页面中有多个网络请求,然后我们需要管理他们的先后请求顺序(当然这里我们可以使用异步串行也可以实现),使用dispatch_group的好处是group中的任务都执行完毕以后会有一个通知回调,我们可以在通知回调里面做我们的操作。例如刷新UI页面,不过这个回调是异步线程里面的,需要回到主线程刷新。
- dispatch_group_notify group中所有任务都执行完毕有的回到
线程组串行队列
//DISPATCH_QUEUE_SERIAL 串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
//创建group
dispatch_group_t group = dispatch_group_create();
//添加队列到group中,执行任务
dispatch_group_async(group, serialQueue1, ^{
NSLog(@"你大爷-->%@",[NSThread currentThread]);
});
dispatch_group_async(group, serialQueue1, ^{
NSLog(@"你二大爷-->%@",[NSThread currentThread]);
});
//group中的任务都执行完毕了的回调,注意这里的回调不是主线程
dispatch_group_notify(group, concurrent1, ^{
//在这里面可以做刷新UI页面等操作,不过要回到主线程中
NSLog(@"你大爷和你二大爷都执行完毕了-->%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
NSLog(@"dispatch_group_wait 结束");
});
看看执行这段代码打印的,这里只开启了一条线程串行执行任务,执行完毕后有通知回调
线程组并行队列
//DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);
//创建group
dispatch_group_t group = dispatch_group_create();
//添加队列到group中,执行任务
dispatch_group_async(group, concurrent1, ^{
for (int i = 0; i<100; i++) {
NSLog(@"你大爷-->%@",[NSThread currentThread]);
}
});
dispatch_group_async(group, concurrent1, ^{
for (int i = 0; i<100; i++) {
NSLog(@"你二大爷-->%@",[NSThread currentThread]);
}
});
//group中的任务都执行完毕了的回调,注意这里的回调不是主线程
dispatch_group_notify(group, concurrent1, ^{
//在这里面可以做刷新UI页面等操作,不过要回到主线程中
NSLog(@"你大爷和你二大爷都执行完毕了-->%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
NSLog(@"dispatch_group_wait 结束");
});
log中可以看出这里面开启了两条线程异步执行任务,执行完毕后有通知回调(log太多没有截图完整)。
4.GCD的一些其他的使用
单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
延迟执行
//<#delayInSeconds#>延迟时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
线程挂起和重启
- dispatch_suspend 挂起 **在创建线程前挂起才有效,当创建的线程已经在执行了就无效了,即GCD无法终止执行中的线程 **
- dispatch_resume 开启
dispatch_queue_t serialQueue = dispatch_queue_create("syncQueue1", DISPATCH_QUEUE_SERIAL);
//挂起线程,此时下面的线程不会开启
dispatch_suspend(serialQueue);
dispatch_async(serialQueue, ^{
//并行队列执行
for (int i = 40; i<50; i++) {
NSLog(@"嘻嘻嘻嘻-->%@",[NSThread currentThread]);
}
});
//休眠4秒后重启线程,这时候才会执行block里面代码块
sleep(4.0);
dispatch_resume(serialQueue2);
dispatch_source_t 实现一个简单的计时器
//倒计时时间
__block int timeout= 5;
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, concurrent1);
//这里面四个参数
//1.dispatch_source_t source 传入上面的source
//2.dispatch_time_t start 开始时间
//3.uint64_t interval 间隔时间
//4.uint64_t leeway 落后时间(设置了貌似没什么效果)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC,0);
dispatch_source_set_event_handler(timer, ^{
if (timeout > 0) {
//在这里执行轮询
NSLog(@"循环中");
timeout--;
}else{
//关闭计时器
dispatch_source_cancel(timer);
}
});
//开启
dispatch_resume(timer);