1.基本概念:
什么是进程:
1)进程是一个具有独立功能的程序关于某次数据集合的一次运行活动,他是操作系统分配资源的基本单元.
2) 进程是指在系统中正在运行的一个应用程序,就是一段程序执行的过程,我们可以理解为手机上的一个app
3)每个进程之间是独立的,每个进程均运行在专用且受保护的内存空间内,拥有独立运行,所需的全部资源
什么是线程
1)程序执行流的最小单元,线程是进程中的一个实体
2)一个进程要想执行任务,必须要至少有一条线程,应用启动的时候,系统会默认开启一条线程,也就是主线程;
线程和进程之间的关系
1)线程是进程的基本执行单位,进程的所有任务必须在线程中执行(主线程,子线程)
2)线程是cpu分配资源的最小单元,一个进程中所有线程共享进程资源
3)一个进程对应多个或者一个线程,但是必须要有线程
队列:这里的队列是指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO的原则,即新任务是被插入到队列末尾,读取总是从头部开始读取,读取一个释放一个
在GCD中有两种队列,串行和并发,遵循FIFO 区别在于自行顺序不同,开启线程数不同
iOS 中的多线程
NSThread,NSoperationQueue,GCD
我们要明确 NSOperationQueue 与 GCD 之间的关系
GCD 是面向底层的 C 语言的 API,NSOpertaionQueue 用 GCD 构建封装的,是 GCD 的高级抽象。
1、GCD 执行效率更高,而且由于队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构,写起 来更方便
2、GCD 只支持 FIFO 的队列,而 NSOperationQueue 可以通过设置最大并发数,设置优先级,添加依赖关系 等调整执行顺序
3、NSOperationQueue 甚至可以跨队列设置依赖关系,但是 GCD 只能通过设置串行队列,或者在队列内添 加 barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
4、NSOperationQueue 因为面向对象,所以支持 KVO,可以监测 operation 是否正在执行(isExecuted)、 是否结束(isFinished)、是否取消(isCanceld)
实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的 且优化完善、运行快速的 GCD 是首选
如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的 代码来实现,而 NSOperationQueue 已经内建了这些支持
不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上 线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而 NSThread 需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销
2、performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(threadFucntion) withObject:nil afterDelay:0];
});
这里的 threadFucntion 方法是不会去执行的,原因在于
这个方法要创建提交任务到 runloop 上的,而 gcd 底层创建的线程是默认没有开启对应 runloop 的,所有 这个方法就会失效。
而如果将 dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启了 runloop 的, 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test 方法也是会去执行的)。
1、问:怎么用 GCD 实现多读单写?
多读单写的意思就是:可以多个读者同时读取数据,而在读的时候,不能去写入数据。并且,在写的过程 中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。
这里的写处理就是通过栅栏的形式去写。 就可以用 dispatch_barrier_sync(栅栏函数)去实现
多读单写实现方式:
-(id)readDataForKey:(NSString*)key{
__blockidresult;
//当前线程和取的线程为同一个
dispatch_queue_t concurrent = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrent, ^{
result = [selfvalueForKey:key];
});
returnresult;
}
-(void)writeDataForKey:(NSString*)key{
dispatch_queue_t concurretn = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(concurretn, ^{
[selfsetValue:@"data"forKey:key];
});
}
介绍:dispatch_group_async
场景:在 n 个耗时并发任务都完成后,再去执行接下来的任务。比如,在 n 个网络请求完成后去刷新 UI 页 面。
dispatch_queue_t concurrentQueen = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for(NSIntegeri =0; i <5; i ++ ) {
dispatch_group_async(group, concurret, ^{
sleep(1);
NSLog(@"网络请求 %ld",i);
});
}
dispatch_group_notify(group,concurrentQueen, ^{
NSLog(@"刷新页面");
});
介绍:Dispatch Semaphore
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。
A 常用做,保持线程同步,将异步执行任务转换为同步执行
B 保持线程安全,为线程加锁
__blockNSIntegernumber =0;
dispatch_semaphore_t semphone = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
number =100;
sleep(10);
dispatch_semaphore_signal(semphone);
});
dispatch_semaphore_wait(semphone, DISPATCH_TIME_FOREVER);
NSLog(@"321321aaaaaaa13231 %ld",number);
发现 过了10秒钟才打印number = 10 ;
说明阻塞了当前线程,将异步执行,转换为同步了
在线程安全中可以将 dispatch_semaphore_wait 看作加锁,而 dispatch_semaphore_signal 看作解锁 首先创建全局变量
@property (nonatomic,strong)dispatch_semaphore_t semphore;
//为线程加锁
-(void)semPhoreLock{
dispatch_semaphore_wait(_semphore, DISPATCH_TIME_FOREVER);
_count++ ;
sleep(1);
NSLog(@"sdad d3232 %d",_count);
dispatch_semaphore_signal(_semphore);
}
-(void)asyncDic{
_semphore = dispatch_semaphore_create(1);
for(inti =0; i <100; i ++ ) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[selfsemPhoreLock];
});
}
}
观察到打印是从1打印100,且dispatch_semaphore_t必须用strong修饰
原因如下:
在子线程中并发执行 asyncTask,那么第一个添加到并发队列里的,会将信号量减 1,此时信号量等于 0, 可以执行接下来的任务。而并发队列中其他任务,由于此时信号量不等于 0,必须等当前正在执行的任务 执行完毕后调用 dispatch_semaphore_signal 将信号量加 1,才可以继续执行接下来的任务,以此类推,从而 达到线程加锁的目的。
延时函数
dispatch_after
dispatch_after 能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指 定时间追加处理到 dispatch_queue
dispatch_once_tonce 实现单例
-(instancetype)shareInstance{
static dispatch_once_tonceToken;
static id instance =nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
NSThread+runloop 实现常驻线程
调用performselector 这个就实现了打印,说明,实现NSThread + RunRoop 实现成功了
自旋锁:是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等 待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住), 那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。在多 CPU 的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。
互斥锁:当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕, 当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁 资源释放锁。互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时 cpu 可以调度其他线程工 作。直到被锁资源释放锁。此时会唤醒休眠线程。