多线程整合
本文知识对iOS开发中多线程的一些知识整合,关于一些概念和技术问题并没有过多的介绍,如果你想了解更多请查看笔者之前写的iOS开发之多线程详解(比较完整):但是有部分涉及到之前文章中没有的技术点和常识,比如加锁的方式,面试相关的,还有一些关于GCD的高级用法,希望你能认真看完,或许可以收获到很多!
http://www.cnblogs.com/iCocos/p/4553103.html
http://www.cnblogs.com/iCocos/p/4553262.html
??先来看看多线程开发中三个非常重要的概念
- 线程(线程)用于指代独立执行的代码段。
- ??进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。 ??
- 任务(task)用于指代抽象的概念,表示需要执行工作。
一点小常识
关于多线程的种类
注意点:
新版iOS中,使用其他线程更新UI可能也能成功,但是不推荐
后面三种对应对应.Net中的多线程、线程池和异步调用
/************************************基本介绍**********************************/
1:pthread:C语言的,实际开发中从来不用,除非你真的闲得蛋疼。这里只是以一个简单的例子应用一下
// 将耗时操作放到子线程中执行
- 第一个参数: 线程的代号(当做就是线程)
- 第二个参数: 线程的属性
- 第三个参数: 指向函数的指针, 就是将来线程需要执行的方法
- 第四个参数: 给第三个参数的指向函数的指针 传递的参数
void *(*functionP)(void *)
void * == id
一般情况下C语言中的类型都是以 _t或者Ref结尾
1 pthread_t threadId; 2 3 // 只要create一次就会创建一个新的线程 4 5 pthread_create(&threadId , NULL, &demo, "lnj");
执行的(函数)方法
1 void *demo(void * param) 2 3 { 4 5 // 会在子线程中执行 6 7 NSLog(@"%s, %@", param, [NSThread currentThread]); 8 9 for (int i = 0; i < 99999; i++) { 10 11 // NSLog是非常耗时的操作 12 13 // 一般情况在企业开发中, 如果程序要上架, 必须去掉程序中所有的NSLog 14 15 NSLog(@"%i", i); 16 17 } 18 19 return NULL; 20 21 }
2:NSThread
可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,
优点:
- - 使用简便
- - 如果通过detach方法创建线程, 不需要手动调用start启动线程 \
- 系统会自动启动
缺点:
- - 不可以进行其它设置
- - 通过detach方法创建子线程是没有返回值的
应用场景:
- - 如果仅仅需要简单的开启一个子线程执行一些操作, 不需要对子线程进行其它设置, 那么推荐使用detach方法创建线程
1 [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"xxx"];
也可以使用对象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,然后调用start方法启动线程。(或者使用:alloc/init)
1 NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"lw"]; 2 3 thread2.name = @"子线程2"; 4 5 thread2.threadPriority = 1.0; 6 7 [thread2 start];
NSObject分类拓展
扩展--NSObject分类扩展方法
为了简化多线程开发过程,苹果官方对NSObject进行分类扩展(本质还是创建NSThread),对于简单的多线程操作可以直接使用这些扩展方法。
- - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg:在后台执行一个操作,本质就是重新创建一个线程执行当前方法.(类上面的第三种方法)
- - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait:在指定的线程上执行一个方法,需要用户创建一个线程对象。
- - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait:在主线程上执行一个方法(前面已经使用过)。
状态:线程状态分为
- isExecuting(正在执行)
- isFinished(已经完成)
- isCancellled(已经取消)
- exist(终止)
[NSThread exit] == return
睡眠;
1 // 注意: 在那个线程中调用sleepForTimeInterval, 那么哪个线程就会被睡 2 3 [NSThread sleepForTimeInterval:2.0]; 4 5 [NSThread sleepUntilDate:[NSDate distantFuture]];
线程中通讯:其实就是相当去在子线程中更新数据,在主线程中刷新UI
比如我们平时在网络上下一张图片显示到界面上,在界面上显示就是其中的通讯:注意刷新UI职能在主线程中,方法有下面几种,但是使用最多的就是第一种。
1 // 开发中常用 2 3 [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; 4 5 6 7 //waitUntilDone: updateImage方法执行完毕,是否立刻执行后面的代码 8 9 [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:NO]; 10 11 12 13 // 可以在指定的线程中, 调用指定对象的指定方法 14 15 [self performSelector:@selector(updateImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
然后再相应的方法中实现UI界面的刷新
关于时间的代码执行计算:
1)
- CFAbsoluteTime begin = CFAbsoluteTimeGetCurrent();
- CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
- NSLog(@"%f", end - begin);
2)
- NSDate *begin = [NSDate date];
- NSDate *end = [NSDate date];
- NSLog(@"%f", [end timeIntervalSinceDate:begin]);
3:NSOperation
NSOperationQueue:
NSInvocationOperation:NSOperation
1 /*创建一个调用操作 2 3 object:调用方法参数 4 5 */ 6 7 NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; 8 9 //创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中 10 11 [invocationOperation start]; 12 13 14 15 //创建操作队列 16 17 NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; 18 19 //注意添加到操作队后,队列会开启一个线程执行此操作 20 21 [operationQueue addOperation:invocationOperation]; 22 23
NSBlockOperation:NSOperation
1 //创建操作队列 2 3 NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; 4 5 operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数 6 7 //创建多个线程用于填充图片 8 9 for (int i=0; i<count; ++i) { 10 11 //方法1:创建操作块添加到队列 12 13 //创建多线程操作 14 15 NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{ 16 17 [self loadImage:[NSNumber numberWithInt:i]]; 18 19 }]; 20 21 //创建操作队列 22 23 24 25 [operationQueue addOperation:blockOperation]; 26 27 28 29 //方法2:直接使用操队列添加操作 30 31 [operationQueue addOperationWithBlock:^{ 32 33 [self loadImage:[NSNumber numberWithInt:i]]; 34 35 }]; 36 37 38 39 }
//添加依赖
- addDependency
1 // NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init]; 2 3 operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数 4 5 6 7 NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{ 8 9 [self loadImage:[NSNumber numberWithInt:(count-1)]]; 10 11 }]; 12 13 //创建多个线程用于填充图片 14 15 for (int i=0; i<count-1; ++i) { 16 17 //方法1:创建操作块添加到队列 18 19 //创建多线程操作 20 21 NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{ 22 23 [self loadImage:[NSNumber numberWithInt:i]]; 24 25 }]; 26 27 //设置依赖操作为最后一张图片加载操作 28 29 [blockOperation addDependency:lastBlockOperation]; 30 31 32 33 [operationQueue addOperation:blockOperation]; 34 35 36 37 } 38 39 //将最后一个图片的加载操作加入线程队列 40 41 [operationQueue addOperation:lastBlockOperation];
注意:
- 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
- 调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。
- 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)。
4:GCD
- 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
- 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
串行
1 dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue对象不是指针类型 2 3 //创建多个线程用于填充图片 4 5 for (int i=0; i<count; ++i) { 6 7 //异步执行队列任务 8 9 dispatch_async(serialQueue, ^{ 10 11 [self loadImage:[NSNumber numberWithInt:i]]; 12 13 }); 14 15 16 17 } 18 19 //非ARC环境请释放 20 21 // dispatch_release(seriQueue); 22 23 }
并发
1 dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 2 3 //创建多个线程用于填充图片 4 5 for (int i=0; i<count; ++i) { 6 7 //异步执行队列任务 8 9 dispatch_async(globalQueue, ^{ 10 11 [self loadImage:[NSNumber numberWithInt:i]]; 12 13 }); 14 15 } 16 17
同步和异步:
- dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, <#^(size_t)block#>)
- dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
线程中通讯:同上。
以下是开发中使用最多的GCD线程中通讯的方式:刷新UI
1 // 1.除主队列以外, 随便搞一个队列 2 3 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 4 5 6 7 // 2.调用异步函数 8 9 dispatch_async(queue, ^{ 10 11 // 1.下载图片 12 13 NSURL *url = [NSURL URLWithString:@"http://pic.4j4j.cn/upload/pic/20130531/07ed5ea485.jpg"]; 14 15 NSData *data = [NSData dataWithContentsOfURL:url]; 16 17 // 2.将二进制转换为图片 18 19 UIImage *image = [UIImage imageWithData:data]; 20 21 22 23 // 3.回到主线程更新UI 24 25 // self.imageView.image = image; 26 27 /* 28 29 技巧: 30 31 如果想等UI更新完毕再执行后面的代码, 那么使用同步函数 32 33 如果不想等UI更新完毕就需要执行后面的代码, 那么使用异步函数 34 35 */ 36 37 dispatch_sync(dispatch_get_main_queue(), ^{ 38 39 self.imageView.image = image; 40 41 }); 42 43 NSLog(@"设置图片完毕 %@", image); 44 45 }); 46 47 48 49
其他方式:
- dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
- dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
- dispatch_time():延迟一定的时间后执行。
- dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
- dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。
//单例:执行一次
1 static dispatch_once_t onceToken; 2 3 dispatch_once(&onceToken, ^{ 4 5 6 7 });
//延迟,相当于after
1 dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>);
//快速遍历,多线程遍历
1 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) { 2 3 4 5 }); 6 7 /* 8 9 for (int i = 0; i < 10; i++) { 10 11 NSLog(@"i = %i", i); 12 13 } 14 15 */ 16 17 /* 18 19 第一个参数: 需要遍历几次 20 21 第二个参数: 决定第三个参数的block在哪个线程中执行 22 23 第三个参数: 回掉 24 25 */ 26 27 28 29 /* 30 31 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) { 32 33 NSLog(@"index = %zd, ==== %@", index, [NSThread currentThread]); 34 35 }); 36 37 */ 38 39 40 41 // 1.定义变量记录原始文件夹和目标文件夹的路径 42 43 NSString *sourcePath = @"/Users/xiaomage/Desktop/test"; 44 45 NSString *destPath = @"/Users/xiaomage/Desktop/lnj"; 46 47 // 2.取出原始文件夹中所有的文件 48 49 NSFileManager *manager = [NSFileManager defaultManager]; 50 51 NSArray *files = [manager subpathsAtPath:sourcePath]; 52 53 // NSLog(@"%@", files); 54 55 // 3.开始拷贝文件 56 57 /* 58 59 for (NSString *fileName in files) { 60 61 // 3.1生产原始文件的绝对路径 62 63 NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName]; 64 65 // 3.2生产目标文件的绝对路径 66 67 NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName]; 68 69 // NSLog(@"%@", sourceFilePath); 70 71 // NSLog(@"%@", destFilePath); 72 73 // 3.3利用NSFileManager拷贝文件 74 75 [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil]; 76 77 } 78 79 */ 80 81 dispatch_apply(files.count, dispatch_get_global_queue(0, 0), ^(size_t index) { 82 83 NSString *fileName = files[index]; 84 85 // 3.1生产原始文件的绝对路径 86 87 NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName]; 88 89 // 3.2生产目标文件的绝对路径 90 91 NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName]; 92 93 // NSLog(@"%@", sourceFilePath); 94 95 // NSLog(@"%@", destFilePath); 96 97 // 3.3利用NSFileManager拷贝文件 98 99 [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil]; 100 101 });
//栅栏(等待前面所有的执行完,自己猜执行)
1 //不能结合全局并发队列使用 2 3 //所有任务都必须在同一个队列中 4 5 dispatch_barrier_sync(dispatch_get_main_queue(), ^{ 6 7 8 9 }); 10 11 dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT); 12 13 // dispatch_queue_t queue2 = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT); 14 15 // dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 16 17 18 19 __block UIImage *image1 = nil; 20 21 __block UIImage *image2 = nil; 22 23 // 1.开启一个新的线程下载第一张图片 24 25 dispatch_async(queue, ^{ 26 27 NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d1632701663f5deb48f8c546479.jpg"]; 28 29 NSData *data = [NSData dataWithContentsOfURL:url]; 30 31 UIImage *image = [UIImage imageWithData:data]; 32 33 image1 = image; 34 35 NSLog(@"图片1下载完毕"); 36 37 }); 38 39 // 2.开启一个新的线程下载第二张图片 40 41 dispatch_async(queue, ^{ 42 43 NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa0f2eb8c0acd3fd1f40345b47.jpg"]; 44 45 NSData *data = [NSData dataWithContentsOfURL:url]; 46 47 UIImage *image = [UIImage imageWithData:data]; 48 49 image2 = image; 50 51 NSLog(@"图片2下载完毕"); 52 53 }); 54 55 56 57 // 3.开启一个新的线程, 合成图片 58 59 // 栅栏 60 61 // 功能: 62 63 // 1.拦截前面的任务, 只有先添加到队列中的任务=执行完毕, 才会执行栅栏添加的任务 64 65 // 2.如果栅栏后面还有其它的任务, 那么必须等栅栏执行完毕才会执行后面的其它任务 66 67 // 注意点: 68 69 // 1.如果想要使用栅栏, 那么就不能使用全局的并发队列 70 71 // 2.如果想使用栅栏, 那么所有的任务都必须添加到同一个队列中 72 73 dispatch_barrier_async(queue, ^{ 74 75 NSLog(@"%@ %@", image1, image2); 76 77 // 1.开启图片上下文 78 79 UIGraphicsBeginImageContext(CGSizeMake(200, 200)); 80 81 // 2.将第一张图片画上去 82 83 [image1 drawInRect:CGRectMake(0, 0, 100, 200)]; 84 85 // 3.将第二张图片画上去 86 87 [image2 drawInRect:CGRectMake(100, 0, 100, 200)]; 88 89 // 4.从上下文中获取绘制好的图片 90 91 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 92 93 // 5.关闭上下文 94 95 UIGraphicsEndImageContext(); 96 97 98 99 // 4.回到主线程更新UI 100 101 dispatch_async(dispatch_get_main_queue(), ^{ 102 103 self.imageView.image = newImage; 104 105 [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/xiaomage/Desktop/lnj/123.png" atomically:YES]; 106 107 }); 108 109 110 111 NSLog(@"栅栏执行完毕了"); 112 113 }); 114 115 116 117 118 119 dispatch_async(queue, ^{ 120 121 NSLog(@"---------"); 122 123 }); 124 125
dispatch_group_t;
dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>);
dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>);
1 dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT); 2 3 4 5 __block UIImage *image1 = nil; 6 7 __block UIImage *image2 = nil; 8 9 10 11 dispatch_group_t group = dispatch_group_create(); 12 13 14 15 // 1.开启一个新的线程下载第一张图片 16 17 dispatch_group_async(group, queue, ^{ 18 19 NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d1632701663f5deb48f8c546479.jpg"]; 20 21 NSData *data = [NSData dataWithContentsOfURL:url]; 22 23 UIImage *image = [UIImage imageWithData:data]; 24 25 image1 = image; 26 27 NSLog(@"图片1下载完毕"); 28 29 }); 30 31 32 33 // 2.开启一个新的线程下载第二张图片 34 35 dispatch_group_async(group, queue, ^{ 36 37 NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa0f2eb8c0acd3fd1f40345b47.jpg"]; 38 39 NSData *data = [NSData dataWithContentsOfURL:url]; 40 41 UIImage *image = [UIImage imageWithData:data]; 42 43 image2 = image; 44 45 NSLog(@"图片2下载完毕"); 46 47 }); 48 49 50 51 // 3.开启一个新的线程, 合成图片 52 53 // 只要将队列放到group中, 队列中的任务执行完毕, group就会发出一个通知 54 55 56 57 dispatch_group_notify(group, queue, ^{ 58 59 NSLog(@"%@ %@", image1, image2); 60 61 // 1.开启图片上下文 62 63 UIGraphicsBeginImageContext(CGSizeMake(200, 200)); 64 65 // 2.将第一张图片画上去 66 67 [image1 drawInRect:CGRectMake(0, 0, 100, 200)]; 68 69 // 3.将第二张图片画上去 70 71 [image2 drawInRect:CGRectMake(100, 0, 100, 200)]; 72 73 // 4.从上下文中获取绘制好的图片 74 75 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 76 77 // 5.关闭上下文 78 79 UIGraphicsEndImageContext(); 80 81 82 83 // 4.回到主线程更新UI 84 85 dispatch_async(dispatch_get_main_queue(), ^{ 86 87 self.imageView.image = newImage; 88 89 [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/xiaomage/Desktop/lnj/123.png" atomically:YES]; 90 91 }); 92 93 });
锁机制:枷锁的几种方法;
@synchronized(self){}互斥锁//其中括号中要么使用self如果不是用self就必须使用同一个对象
1 NSData *data; 2 3 NSString *name; 4 5 //线程同步 6 7 @synchronized(self){ 8 9 if (_imageNames.count>0) { 10 11 name=[_imageNames lastObject]; 12 13 [NSThread sleepForTimeInterval:0.001f]; 14 15 [_imageNames removeObject:name]; 16 17 } 18 19 } 20 21 if(name){ 22 23 NSURL *url=[NSURL URLWithString:name]; 24 25 data=[NSData dataWithContentsOfURL:url]; 26 27 } 28 29 return data;
NSLock同步锁(unlock,lock)
- nonatomic:非原子:(setter)不枷锁——iOS开发中99%都是使用这个
- atomic:原子:(setter)枷锁-》资源抢夺的时候会使用到他:自旋锁(和互斥锁的区别是不会睡眠,只是等待)
- nonatomic属性读取的是内存数据(寄存器计算好的结果),而atomic就保证直接读取寄存器的数据
创建一个原子属性
1 @property (atomic,strong) NSMutableArray *imageNames; 2 3
创建一个NSLock成员变量
1 NSLock *_lock;
实现NSLock枷锁
1 //加锁 2 3 [_lock lock]; 4 5 if (_imageNames.count>0) { 6 7 name=[_imageNames lastObject]; 8 9 [_imageNames removeObject:name]; 10 11 } 12 13 //使用完解锁 14 15 [_lock unlock]; 16 17
如果开发中用到的关于加锁相关的,那么基本上就是上面两种中的一种去使用,具体使用哪一种看个人,笔者比较倾向前一种,简单,粗暴!
可以通过tryLock方法,此方法会返回一个BOOL型的值,如果为YES说明获取锁成功,否则失败。另外还有一个lockBeforeData:方法指定在某个时间内获取锁,同样返回一个BOOL值,如果在这个时间内加锁成功则返回YES,失败则返回NO。
一种是使用@synchronized代码块
之前还提到的nonatomic,atomic关于原子锁,区别开来:其实就是结合同步锁的使用
其他锁:
- NSRecursiveLock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复 获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
- NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。
- pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。
- NSConditionLock:条件锁
- NSCondition来控制线程通信
- NSCondition实现了NSLocking协议,所以它本身也有lock和unlock方法,因此也可以将它作为NSLock解决线程同步问题
- NSCondition更重要的是解决线程之间的调度关系
-
- wati方法控制某个线程处于等待状态
- signal(此方法唤醒一个线程
- broadcast(此方法会唤醒所有等待线程)
GCD是一种信号机制
GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待
1 /*信号等待 2 3 第二个参数:等待时间 4 5 */ 6 7 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 8 9 //信号通知 10 11 dispatch_semaphore_signal(_semaphore); 12 13
/*****************************************使用总结*********************************************/
关于多线程使用总结:
- 1>无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度(CPU空闲时就会执行)。
- 2>更新UI应该在主线程(UI线程)中进行,并且推荐使用同步调用,常用的方法如下:
- - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread])
- [NSOperationQueue mainQueue] addOperationWithBlock:
- dispatch_sync(dispatch_get_main_queue(), ^{})
- 3>NSThread适合轻量级多线程开发,控制线程顺序比较难,同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)。
- 4>对于简单的多线程开发建议使用NSObject的扩展方法完成,而不必使用NSThread。
- 5>可以使用NSThread的currentThread方法取得当前线程,使用 sleepForTimeInterval:方法让当前线程休眠。
- 6>NSOperation进行多线程开发可以控制线程总数及线程依赖关系。
- 7>创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。
- 8>相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。
- 9>NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。
- 10>在GCD中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。
- 11>在GDC中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)。
- 12>相比使用NSLock,@synchronized更加简单,推荐使用后者。
/************************************常用方法与总结**************************************************************/
//子线程
1 //pthread 2 3 pthread_t pt; 4 5 pthread_create(&pt, NULL, &iCocos, @"iCocos"); 6 7 void *iCocos(void * param);//{} 8 9 10 11 //pthread_mutex_lock(<#pthread_mutex_t *#>) 12 13
//NSThread;
1 NSThread *thread1 = [[NSThread alloc] init]; 2 3 NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(ios) object:@"iCocos"]; 4 5 [NSThread detachNewThreadSelector:@selector(ios) toTarget:self withObject:@"iCocos"]; 6 7 [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]; 8 9 [self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>]; 10 11 [self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]; 12 13
//NSOperation;
1 NSOperationQueue *queue = [NSOperationQueue mainQueue]; 2 3 4 5 NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(ios) object:@"iCocos"]; 6 7 [operation1 start]; 8 9 [queue addOperation:operation1]; 10 11 12 13 14 15 NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{ 16 17 }]; 18 19 [queue addOperation:block]; 20 21 22 23 24 25 [queue addOperationWithBlock:^{ 26 27 28 29 }];
//GCD
1 dispatch_queue_t main = dispatch_get_main_queue(); 2 3 dispatch_sync(main, ^{ 4 5 6 7 }); 8 9 10 11 //串行 12 13 dispatch_queue_t searlizer = dispatch_queue_create("iCocos", DISPATCH_QUEUE_SERIAL); 14 15 dispatch_async(sear, ^{ 16 17 18 19 }); 20 21 22 23 //并发 24 25 dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //DISPATCH_QUEUE_CONCURRENT 26 27 dispatch_async(global, ^{ 28 29 30 31 }); 32 33 34 35 36 37 //UI刷新 38 39 dispatch_queue_t gl = dispatch_get_global_queue(0, 0); 40 41 dispatch_async(gl, ^{ 42 43 44 45 dispatch_sync(dispatch_get_main_queue(), ^{ 46 47 48 49 }); 50 51 52 53 });
//回到主线程:
1 [self performSelector:<#(SEL)#> onThread:[main] withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]; 2 3 [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]; 4 5 dispatch_sync(dispatch_get_main_queue(), ^{ 6 7 8 9 }); 10 11
//延迟
1 [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> invocation:<#(NSInvocation *)#> repeats:<#(BOOL)#>]; 2 3 [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(id)#> selector:<#(SEL)#> userInfo:<#(id)#> repeats:<#(BOOL)#>]; 4 5 6 7 [self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>]; 8 9 10 11 [dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 12 13 <#code to be executed after a specified delay#>]; 14 15 });
/****************************************面试之重点************************************************/
关于同步,异步-并发,串行和主队列
异步加并发:
1 /* 2 3 异步 + 并发 : 会开启新的线程 4 5 如果任务比较多, 那么就会开启多个线程 6 7 */ 8 9 - (void)asynConcurrent 10 11 { 12 13 /* 14 15 执行任务 16 17 dispatch_async 18 19 dispatch_sync 20 21 */ 22 23 24 25 /* 26 27 第一个参数: 队列的名称 28 29 第二个参数: 告诉系统需要创建一个并发队列还是串行队列 30 31 DISPATCH_QUEUE_SERIAL :串行 32 33 DISPATCH_QUEUE_CONCURRENT 并发 34 35 */ 36 37 // dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT); 38 39 40 41 // 系统内部已经给我们提供好了一个现成的并发队列 42 43 /* 44 45 第一个参数: iOS8以前是优先级, iOS8以后是服务质量 46 47 iOS8以前 48 49 * - DISPATCH_QUEUE_PRIORITY_HIGH 高优先级 2 50 51 * - DISPATCH_QUEUE_PRIORITY_DEFAULT: 默认的优先级 0 52 53 * - DISPATCH_QUEUE_PRIORITY_LOW: 低优先级 -2 54 55 * - DISPATCH_QUEUE_PRIORITY_BACKGROUND: 56 57 58 59 iOS8以后 60 61 * - QOS_CLASS_USER_INTERACTIVE 0x21 用户交互(用户迫切想执行任务) 62 63 * - QOS_CLASS_USER_INITIATED 0x19 用户需要 64 65 * - QOS_CLASS_DEFAULT 0x15 默认 66 67 * - QOS_CLASS_UTILITY 0x11 工具(低优先级, 苹果推荐将耗时操作放到这种类型的队列中) 68 69 * - QOS_CLASS_BACKGROUND 0x09 后台 70 71 * - QOS_CLASS_UNSPECIFIED 0x00 没有设置 72 73 74 75 第二个参数: 废物 76 77 */ 78 79 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 80 81 82 83 /* 84 85 第一个参数: 用于存放任务的队列 86 87 第二个参数: 任务(block) 88 89 90 91 GCD从队列中取出任务, 遵循FIFO原则 , 先进先出 92 93 输出的结果和苹果所说的原则不符合的原因: CPU可能会先调度其它的线程 94 95 96 97 能够创建新线程的原因: 98 99 我们是使用"异步"函数调用 100 101 能够创建多个子线程的原因: 102 103 我们的队列是并发队列 104 105 */ 106 107 dispatch_async(queue, ^{ 108 109 NSLog(@"任务1 == %@", [NSThread currentThread]); 110 111 }); 112 113 dispatch_async(queue, ^{ 114 115 NSLog(@"任务2 == %@", [NSThread currentThread]); 116 117 }); 118 119 dispatch_async(queue, ^{ 120 121 NSLog(@"任务3 == %@", [NSThread currentThread]); 122 123 }); 124 125 }
异步加串行:
1 /* 2 3 异步 + 串行:会开启新的线程 4 5 但是只会开启一个新的线程 6 7 注意: 如果调用 异步函数, 那么不用等到函数中的任务执行完毕, 就会执行后面的代码 8 9 */ 10 11 - (void)asynSerial 12 13 { 14 15 // 1.创建串行队列 16 17 dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_SERIAL); 18 19 /* 20 21 能够创建新线程的原因: 22 23 我们是使用"异步"函数调用 24 25 只创建1个子线程的原因: 26 27 我们的队列是串行队列 28 29 */ 30 31 // 2.将任务添加到队列中 32 33 dispatch_async(queue, ^{ 34 35 NSLog(@"任务1 == %@", [NSThread currentThread]); 36 37 }); 38 39 dispatch_async(queue, ^{ 40 41 NSLog(@"任务2 == %@", [NSThread currentThread]); 42 43 }); 44 45 dispatch_async(queue, ^{ 46 47 NSLog(@"任务3 == %@", [NSThread currentThread]); 48 49 }); 50 51 52 53 NSLog(@"--------"); 54 55 }
同步加串行:
1 /* 2 3 同步 + 串行: 不会开启新的线程 4 5 注意点: 如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码 6 7 */ 8 9 - (void)syncSerial 10 11 { 12 13 // 1.创建一个串行队列 14 15 // #define DISPATCH_QUEUE_SERIAL NULL 16 17 // 所以可以直接传NULL 18 19 dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", NULL); 20 21 22 23 // 2.将任务添加到队列中 24 25 dispatch_sync(queue, ^{ 26 27 NSLog(@"任务1 == %@", [NSThread currentThread]); 28 29 }); 30 31 dispatch_sync(queue, ^{ 32 33 NSLog(@"任务2 == %@", [NSThread currentThread]); 34 35 }); 36 37 dispatch_sync(queue, ^{ 38 39 NSLog(@"任务3 == %@", [NSThread currentThread]); 40 41 }); 42 43 44 45 NSLog(@"---------"); 46 47 } 48 49
同步加并发:
1 /* 2 3 同步 + 并发 : 不会开启新的线程 4 5 妻管严 6 7 */ 8 9 - (void)syncConCurrent 10 11 { 12 13 // 1.创建一个并发队列 14 15 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 16 17 18 19 // 2.将任务添加到队列中 20 21 dispatch_sync(queue, ^{ 22 23 NSLog(@"任务1 == %@", [NSThread currentThread]); 24 25 }); 26 27 dispatch_sync(queue, ^{ 28 29 NSLog(@"任务2 == %@", [NSThread currentThread]); 30 31 }); 32 33 dispatch_sync(queue, ^{ 34 35 NSLog(@"任务3 == %@", [NSThread currentThread]); 36 37 }); 38 39 40 41 NSLog(@"---------"); 42 43 } 44 45
异步加主队咧:
1 /* 2 3 异步 + 主队列 : 不会创建新的线程, 并且任务是在主线程中执行 4 5 */ 6 7 - (void)asyncMain 8 9 { 10 11 // 主队列: 12 13 // 特点: 只要将任务添加到主队列中, 那么任务"一定"会在主线程中执行 \ 14 15 无论你是调用同步函数还是异步函数 16 17 dispatch_queue_t queue = dispatch_get_main_queue(); 18 19 20 21 dispatch_async(queue, ^{ 22 23 NSLog(@"%@", [NSThread currentThread]); 24 25 }); 26 27 } 28 29
主线程中同步加主队列:
1 /* 2 3 如果是在主线程中调用同步函数 + 主队列, 那么会导致死锁 4 5 导致死锁的原因: 6 7 sync函数是在主线程中执行的, 并且会等待block执行完毕. 先调用 8 9 block是添加到主队列的, 也需要在主线程中执行. 后调用 10 11 */ 12 13 - (void)syncMain 14 15 { 16 17 NSLog(@"%@", [NSThread currentThread]); 18 19 // 主队列: 20 21 dispatch_queue_t queue = dispatch_get_main_queue(); 22 23 24 25 // 如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码 26 27 // 注意: 如果dispatch_sync方法是在主线程中调用的, 并且传入的队列是主队列, 那么会导致死锁 28 29 dispatch_sync(queue, ^{ 30 31 NSLog(@"----------"); 32 33 NSLog(@"%@", [NSThread currentThread]); 34 35 }); 36 37 NSLog(@"----------"); 38 39 }
子线程中同步加主队列:
1 /* 2 3 如果是在子线程中调用 同步函数 + 主队列, 那么没有任何问题 4 5 */ 6 7 - (void)syncMain2 8 9 { 10 11 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 12 13 dispatch_async(queue, ^{ 14 15 // block会在子线程中执行 16 17 // NSLog(@"%@", [NSThread currentThread]); 18 19 20 21 dispatch_queue_t queue = dispatch_get_main_queue(); 22 23 dispatch_sync(queue, ^{ 24 25 // block一定会在主线程执行 26 27 NSLog(@"%@", [NSThread currentThread]); 28 29 }); 30 31 }); 32 33 NSLog(@"------------"); 34 35 } 36 37