多线程的缺点:
1.拥有多个可执行的路径
2.线程问题难以调试
3.数据安全受到一定的影响,一个存,一个删的时候;
方法一 、
GCD:GCD队列始终是FIFO(先进先出)的方法来处理任务,但是任务执行的时间并不相同,因此先处理的任务不一定先结束,所以用底层的线程池来处理和管理用户提交的任务,串行队列线程池之需要维护一个线程即可,并发队列线程池需要维护多个线程;
创建队列和访问队列:
dispatch_queue_t dispatch_get_current_queue()获取当前执行代码所在队列
dispatch_queue_t dispatch_get_global_queue(0, 0)全局并发队列,参数1:优先级 (#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN),参数2:预留参数;
dispatch_queue_t dispatch_get_main_queue()主线程关联的串行队列;
char * name= dispatch_queue_get_label(queue);获取指定队列标签;
创建一个队列
dispatch_queue_t que=dispatch_queue_create("createQueue", DISPATCH_QUEUE_SERIAL);创建一个串行队列,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁
dispatch_queue_t que1=dispatch_queue_create("createrQueue", DISPATCH_QUEUE_CONCURRENT);创建一个并行队列;
开启一个子线程,
1.
1> GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。
2> 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行
3> 代码实现,比如异步下载图片后,回到主线程显示图片
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//dispatch_get_global_queue(0, 0)获取系统的全局并发队列;
//进行一些耗时的操作;
dispatch_async(dispatch_get_main_queue(), ^{
// dispatch_get_main_queue() 获取主线程关联的串行队列;
//主线程中更新UI,假如允许子线程访问,修改UI,这就需要对多个新线程的并发访问进行同步控制,否则,多个线程将破坏UI的完整性;
});
});
2.
// 一次性执行:
static dispatch_once_t onceToken;// 该静态变量用于标识是否已经执行;
dispatch_once(&onceToken, ^{
// code to be executed once
});
3.
// 延迟2秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
4.
// 自定义dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
// your code
});
dispatch_release(urls_queue);
5. // 合并汇总结果
Dispatch Group的使用
假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现
虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:
我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
下面用Dispatch Group优化上面的代码:
dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
6.并发执行循环迭代,提高性能
GCD的另一个用处是可以让程序在后台较长久的运行。
1.当应用进入后台时,如果程序有些状态数据没有保存,而IOS可能在内存紧张的时候终止该应用程序,那么久可能导致应用丢失状态数据的情况,
2.当应用程序转入后台后,不要在主线程中执行超过5秒的任务,这是应用有可能从内存中被删除了;
3.如果进入后台后,程序正在执行下载或文件传输,告诉系统进入后台后还有更多任务需要完成,从而向系统申请更多的时间,这样当我们的程序进入后台后,及时用户在使用其它应用的时候,只要系统还有足够的内存,我们的应用就可以保存在内存中;
向后台请求更多时间;
在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
让程序在后台长久运行的示例代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
//监听是否进入后台;
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enterBack:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
}
-(void)enterBack:(NSNotification*)notification
{
UIApplication * app=[UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier backTaskId;//定义标识
//向系统请求更多后台执行时间;
backTaskId=[app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:backTaskId];
}];
if (backTaskId==UIBackgroundTaskInvalid) {
NSLog(@"IOS版本不支持后台运行,后台任务启动失败");
return;
}
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<100; i++) {
NSLog(@"下载任务完成:%d",i);
// 模拟正在下载的操场;
[NSThread sleepForTimeInterval:10];
}
NSLog(@"剩余的后台任务时间为:%f", app.backgroundTimeRemaining);
//结束后台任务
[app endBackgroundTask:backTaskId];
});
}
方法二、
NSOperation和NSOperationQueue(在GCD的基础上做的,比GCD稍慢)
NSOperationQueue:底层维护一个线程池,是一个FIFO(先进先出)的队列,它负责提交多个NSOperation操作
NSOperation: nsoperation一般不会拿来直接使用,而是选择它的子类NSInvocationOperation 和 NSBlockOperation
NSString * url=@"http://www.gg/logo.jpg";
NSInvocationOperation * operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImageWithUrl:) object:url];
-(void)downloadImageWithUrl:(NSString*)url
{}
方法三,更轻量级,更偏向底层的多线程实现方法,需要自己管理线程的生命周期,线程的同步;
NSthread:
- (void)viewDidLoad {
[super viewDidLoad];
for (int i=0; i<100; i++) {
NSLog(@"====%@===%d",[NSThread currentThread],i);
if (i==20) {
//init创建的线程必须调用start方法启动进入准备状态,等待系统调度,调度是具有随机性的;
NSThread * thread=[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
[thread start];
//detachNewThreadSeletor创建的同时就启动进入准备状态,等待系统调度;
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
}
}
-(void)run{
for (int i=0; i<100; i++) {
NSLog(@"------%@----%d",[NSThread currentThread],i);
}
}
s
线程状态:
当线程被创建并被启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,当进入执行状态后也不是一直占用CUP独自运行,所以CPU需要在多个线程之间切换,线程状态也会多次在运行,准备状态之间切换
如果希望线程启动时就直接进入执行状态,程序可以使用 [NSThread sleepForTimeInterval:0.001];让运行的线程进入阻塞状态(主线程)睡眠1毫秒——1秒就够了,因为在这1毫秒内CPU是不会空闲的,它回去执行处于准备状态的线程,这样就可以让创建的子线程立即执行
线程的终止
1.正常结束终止,2,错误终止,3调用exit方法终止;
测试某个线程是否在运行,调用线程对象的isExcutiong.isFinished方法;
NSThread并没有提供方法来终止某个子线程,要终止线程先调用[thread cancel],改变线程状态,利用isCancelled,判断状态,如果为Yes,调用[NSThread exit];
真正终止线程;
线程睡眠:如果要让线程暂停一段时间,并进入阻塞状态,[NSThread sleepForTimeInterval:0.001],[NSThread sleepUntilDate:]
线程优先级:线程优先级double(0-1.0,每个子线程的默认优先级为0.5;thread.name=@"A"; thread.threadPriority=0.8;值越大优先级越高;
线程同步:
@sychronized
{ //需要进行同步处理的代码}
代码块可以实现同步,保证代码安全性,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该对象会释放对同步监视器的锁定;
同步锁:
NSLock * lock=[[NSLock alloc]init];
[lock lock];
[lock unlock];
使用NSCondition控制线程同步通信:NSCondition 可以让那些已经锁定的NSCondition对象却无法继续使用执行的线程释放NSCondition对象,NSCondition对象也可以唤醒其他处于等待状态的线程;
-wait :导致线程当前线程一直等待,直到其他线程调用NSCondition的signal方法或broadcast方法唤醒该线程,waitUntilDtae:
-signal:唤醒等待的单个线程;选择是任意性的,
-broadcast:唤醒所有线程;
#import "Acount.h"
@implementation Acount
-(instancetype)init
{
self=[super init ];
if (self) {
_cond=[NSCondition new];
}
return self;
}
-(instancetype)initWithCountNo:(NSString *)acountNo balance:(float)balance
{
self=[super init];
if (self) {
_cond=[NSCondition new];
_acountNo=acountNo;
_balance=balance;
}
return self;
}
-(void)draw:(float)drawNum
{
[_cond lock];
//flag=no没钱取钱阻塞;
if (drawNum>_balance) {
_flag=NO;
}
else
{
_flag=YES;
}
if (!_flag) {
NSLog(@"等待存钱drawNum:%f,_balance:%f",drawNum,_balance);
[_cond wait];
}else
{
NSLog(@"%@取钱:%g", [NSThread currentThread].name,drawNum);
_balance-=drawNum;
NSLog(@"账户余额:%g",_balance);
[_cond broadcast];
}
[_cond unlock];
}
-(void)deposit:(float)depositNum
{
[_cond lock];
NSLog(@"%@存钱:%g", [NSThread currentThread].name,depositNum);
_balance+=depositNum;
NSLog(@"账户余额:%g",_balance);
[_cond broadcast];
[_cond unlock];
}
===============================================================================================================================
- (void)viewDidLoad {
[super viewDidLoad];
_account=[[Acount alloc]initWithCountNo:@"123" balance:1000.0];
}
- (IBAction)DepositAndDraw:(id)sender {
[NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber numberWithDouble:800.0]];
[NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber numberWithDouble:800.0]];
[NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber numberWithDouble:800.0]];
[NSThread detachNewThreadSelector:@selector(depositMethod:) toTarget:self withObject:[NSNumber numberWithDouble:800.0]];
}
-(void)depositMethod:(NSNumber*)depositNum
{
[NSThread currentThread].name=@"A存钱";
for (int i=0; i<100; i++) {
[_account deposit:[depositNum floatValue]];
}
}
-(void)drawMethod:(NSNumber*)drawNum
{
[NSThread currentThread].name=@"B取钱";
for (int i=0 ; i<100; i++) {
[_account draw:[drawNum floatValue]];
}
}