多线程
2016-03-28 15:13
281 查看
先说一下什么叫做线程
一个运行着的程序就是一个进程,一个进程至少包括一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线程在程序中的地位和其他线程不同,它是其他线程的父线程,且所有界面的显示操作即AppKit或UIKit的操作必须在主线程进行。
系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间,每创建一个新的线程,都需要一些内存和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。
iOS有三种多线程编程技术,分别为:
此时的任务不会立刻执行,而是启动线程,可以设置线程的一些属性。系统会强引用该线程,直到线程死亡(任务执行完毕或强制关闭) [thread start];线程一旦启动就会执行任务!
2.创建一个线程自启动
这个方法会直接开启新线程,并执行任务,无返回值,拿不到创建的线程
3.隐式开启线程并自启动
这个方法会直接开启新线程,并执行任务,无返回值,拿不到创建的线程
4.线程常用方法:
//获取当前线程
[NSThread currentThread];
//获取当前主线程
5.控制线程的状态:
sleep:进入阻塞状态
exit:关闭线程,进入死亡状态
6.线程间通信常用方法
7.多线程数据访问可能出现的问题:
不同线程,在同一时刻访问同一块内存,可能会导致数据出错
解决方案,对可能会同时访问同一块内存的代码加锁( @synchronized(<#token#>) {
<#statements#>
})同一时刻最多只能有一条线程访问这块内存,
互斥锁:对一段代码加锁后,同一时刻最多只能有一条线程执行加锁代码,不过这样会造成系统开销。
二、NSOperation和NSOperationQueue
NSOperation是苹果公司对GCD的封装,完全面向对象,所以使用起来更好理解。
NSOperation只是一个抽象类,所以不能封装任务。但它有2个之类用于封装任务。分别是:NSInvocationOperation和NSBlockOperation。
NSOperation和NSOperationQueue实现多线程的具体步骤
1.先将需要执行的操作封装到一个NSOperation对象中
2.然后将NSOperation对象添加到NSOperationQueue中
3.系统会自动将NSOperationQueue中的NSOperation取出来
4.将取出的NSOperation封装的操作放到一条新线程中执行
队列
1.mainQueue:主队列
获取方式: [NSOperationQueue mainQueue]
任务添加到主队列后,只会被分配到主线程执行所以任务一定会是串行
2.自己创建的队列
NSInvocationOperation:需要传入一个方法名
这样的任务,会默认在当前线程执行。但是NSBlockOperation还有一个方法addExecutionBlock,通过这个方法可以给Operation添加多个执行Block。这样Operation中的任务就会并发执行,它会在主线程和其他多个线程执行这些任务。
注意addExecutionBlock方法必须在start()方法之前执行,否则会报错。
自定义Operation继承NSOperation
自定义Operation需要继承NSOperation类,并实现其main方法,因为在调用start方法的时候,内部会调用main方法完成相关逻辑。
队列操作:
1.取消任务:取消队列的所有任务-(void)cancelAllOperations;,也可以调用NSOperation的-(void)cancel方法取消单个操作
2.注意:取消操作的时候,只会取消还没有执行的操作,已经在执行的操作会执行完毕,所以,在自定义Operation的时候,每开始一个耗时操作,就检测当前操作是否已经取消([self isCancelled]),取消了就return,操作一旦取消就不能恢复。
3.暂停和恢复队列
-(void)setSuspended:(BOOL)b;//YES表示暂停,NO表示恢复
-(BOOL)isSuspended;//判断是否被暂停
NSOperation依赖:
1.添加依赖:调用addDependency方法添加依赖。
2.一个必须等到它依赖的操作执行完毕了,才能执行这个操作
3.操作是可以跨队列依赖的
4.不要互相依赖,不然操作执行不了
以下是依赖实例
NSOperationQueue有一个参数maxConcurrentOperationCount最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为1的时候,就是串行!
三、GCD (Grand Central Dispatch)
NSTread和NSOperation由于苹果官方不怎么推荐使用,而且缺点也比较多,所以我没怎么用,也就不介绍了。接下来我们重点学习GCD的使用
GCD是一种纯C语言的,提供了非常多强大的函数,它的组建风格为面向对象。GCD的API很大程度上是基于block,当然GCD也可以脱离block来使用,比如使用传统的C机制提供函数指针。但事实证明,配合block使用时的效率是最高的!GCD还能自动根据系统负载来增减线程数量,这就会减少上下文切换和加快了计算速率!
GCD对象被称为dispatch object。使用dispatch——release和dispatch——retain函数来操作dispatch object的引用计数来进行内存管理。但dispatch object不参与垃圾回收机制,所以即便开启了GCD,你也必须手动管理GCD对象内存。
在GCD中加入了2个非常重要的概念:任务,队列。
任务:即操作,在GCD中就是一个block,所以添加任务十分方便。任务有2种执行方式:异步执行和同步执行,他们的区别是,是否会创建新的线程。
同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到block任务执行完毕!
同步操作会阻塞当前线程并等待block中任务执行完毕,然后当前线程才会继续运行。
异步操作则会直接往下执行,不会阻塞当前线程
队列:用于存放任务。分为串行队列和并行队列
串行队列中的任务会根据队列的定义FIFO的执行,一个接一个的先进先出进行执行
并行队列中任务也会FIFO的执行,但是它取出来一个就会放到别的线程,然后再取出来一个又放到另一个线程,这样由于取得动作很快,所以看起来就像所有任务一起执行的。不过GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有的任务同时执行。
同步执行
异步执行
串行队列
当前线程,一个一个执行
并行队列
当前线程,一个一个执行
开很多线程,一起执行
创建队列(线程)
主队列(主线程):这是一个特殊的串行队列。它用于刷新UI,任何需要刷新UI的工作都在主队列执行,所以一般耗时的任务都要放到别的线程执行。
dispatch_queue_t queue = dispatch_get_main_queue();
自己创建的队列:自己可以创建串行队列,并行队列。 其中第一个参数是标识符,用于debug的时候标识唯一队列,可以为空。第二个参数是用于表示创建的队列是串行还是并行,传入 DISPATCH_QUEUE_SERIAL 或者 null表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT表示创建并行队列。
全局队列:只要是并行任务一般都加到这个队列中,这个系统提供的一个并发队列
创建任务:同步任务:会阻塞当前线程(SYNC)
异步任务:不会阻塞当前线程(ASYNC)
队列组(线程组):队列组可以将很多队列添加到一个组里面,这样当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
//创建队列组
dispatch_group_t group = dispatch_group_create();
//创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//多次使用队列组的方法执行任务,只有异步方法
dispatch_group_async(group, queue, ^{
for (int i =0; i<3; i++) {
NSLog(@”group1 = %@”,[NSThread currentThread]);
}
});
关于GCD还有2点:
1. dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
这个方法的传入的queue如果是 DISPATCH_QUEUE_CONCURRENT参数创建的queue时,这个方法会阻塞这个queue(是阻塞queue,而不是当前线程),一直等到这个queue中排在它前面的任务都执行完才会执行自己,自己执行完毕后会取消阻塞,使这个queue中排在它后面的任务继续执行。如果传入的是其他queue则和dispatch_async一样;
2.
这个方法的使用和第一个一样,传入自定义的并发队列DISPATCH_QUEUE_CONCURRENT,它也会阻塞queue,并且还会阻塞当前线程。
一个运行着的程序就是一个进程,一个进程至少包括一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线程在程序中的地位和其他线程不同,它是其他线程的父线程,且所有界面的显示操作即AppKit或UIKit的操作必须在主线程进行。
系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间,每创建一个新的线程,都需要一些内存和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。
iOS有三种多线程编程技术,分别为:
一、NSTread
1.创建一个线程,然后启动NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
此时的任务不会立刻执行,而是启动线程,可以设置线程的一些属性。系统会强引用该线程,直到线程死亡(任务执行完毕或强制关闭) [thread start];线程一旦启动就会执行任务!
2.创建一个线程自启动
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
这个方法会直接开启新线程,并执行任务,无返回值,拿不到创建的线程
3.隐式开启线程并自启动
[self performSelectorInBackground:@selector(run) withObject:nil];
这个方法会直接开启新线程,并执行任务,无返回值,拿不到创建的线程
4.线程常用方法:
//获取当前线程
[NSThread currentThread];
//获取当前主线程
[NSThread mainThread];
//设置线程名称,方便调试
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil]; [thread setName:@"ceshi"];
//获取线程的名称
[thread name];
5.控制线程的状态:
sleep:进入阻塞状态
exit:关闭线程,进入死亡状态
6.线程间通信常用方法
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait -(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait
7.多线程数据访问可能出现的问题:
不同线程,在同一时刻访问同一块内存,可能会导致数据出错
解决方案,对可能会同时访问同一块内存的代码加锁( @synchronized(<#token#>) {
<#statements#>
})同一时刻最多只能有一条线程访问这块内存,
互斥锁:对一段代码加锁后,同一时刻最多只能有一条线程执行加锁代码,不过这样会造成系统开销。
二、NSOperation和NSOperationQueue
NSOperation是苹果公司对GCD的封装,完全面向对象,所以使用起来更好理解。
NSOperation只是一个抽象类,所以不能封装任务。但它有2个之类用于封装任务。分别是:NSInvocationOperation和NSBlockOperation。
NSOperation和NSOperationQueue实现多线程的具体步骤
1.先将需要执行的操作封装到一个NSOperation对象中
2.然后将NSOperation对象添加到NSOperationQueue中
3.系统会自动将NSOperationQueue中的NSOperation取出来
4.将取出的NSOperation封装的操作放到一条新线程中执行
队列
1.mainQueue:主队列
获取方式: [NSOperationQueue mainQueue]
任务添加到主队列后,只会被分配到主线程执行所以任务一定会是串行
2.自己创建的队列
NSInvocationOperation:需要传入一个方法名
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil]; [operation start]; NSBlockOperation: NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); }]; [blockOperation start];
这样的任务,会默认在当前线程执行。但是NSBlockOperation还有一个方法addExecutionBlock,通过这个方法可以给Operation添加多个执行Block。这样Operation中的任务就会并发执行,它会在主线程和其他多个线程执行这些任务。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); }]; for (int i = 0; i<5; i++) { [blockOperation addExecutionBlock:^{ NSLog(@"第%d次: %@",i,[NSThread currentThread]); }]; } [blockOperation start];
注意addExecutionBlock方法必须在start()方法之前执行,否则会报错。
自定义Operation继承NSOperation
自定义Operation需要继承NSOperation类,并实现其main方法,因为在调用start方法的时候,内部会调用main方法完成相关逻辑。
队列操作:
1.取消任务:取消队列的所有任务-(void)cancelAllOperations;,也可以调用NSOperation的-(void)cancel方法取消单个操作
2.注意:取消操作的时候,只会取消还没有执行的操作,已经在执行的操作会执行完毕,所以,在自定义Operation的时候,每开始一个耗时操作,就检测当前操作是否已经取消([self isCancelled]),取消了就return,操作一旦取消就不能恢复。
3.暂停和恢复队列
-(void)setSuspended:(BOOL)b;//YES表示暂停,NO表示恢复
-(BOOL)isSuspended;//判断是否被暂停
NSOperation依赖:
1.添加依赖:调用addDependency方法添加依赖。
2.一个必须等到它依赖的操作执行完毕了,才能执行这个操作
3.操作是可以跨队列依赖的
4.不要互相依赖,不然操作执行不了
以下是依赖实例
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"任务1 %@",[NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"任务2 %@",[NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"任务2 %@",[NSThread currentThread]); [NSThread sleepForTimeInterval:1.0]; }]; [operation1 addDependency:operation ]; [operation2 addDependency:operation1]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperations:@[operation2,operation1,operation] waitUntilFinished:NO];
NSOperationQueue有一个参数maxConcurrentOperationCount最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为1的时候,就是串行!
三、GCD (Grand Central Dispatch)
NSTread和NSOperation由于苹果官方不怎么推荐使用,而且缺点也比较多,所以我没怎么用,也就不介绍了。接下来我们重点学习GCD的使用
GCD是一种纯C语言的,提供了非常多强大的函数,它的组建风格为面向对象。GCD的API很大程度上是基于block,当然GCD也可以脱离block来使用,比如使用传统的C机制提供函数指针。但事实证明,配合block使用时的效率是最高的!GCD还能自动根据系统负载来增减线程数量,这就会减少上下文切换和加快了计算速率!
GCD对象被称为dispatch object。使用dispatch——release和dispatch——retain函数来操作dispatch object的引用计数来进行内存管理。但dispatch object不参与垃圾回收机制,所以即便开启了GCD,你也必须手动管理GCD对象内存。
在GCD中加入了2个非常重要的概念:任务,队列。
任务:即操作,在GCD中就是一个block,所以添加任务十分方便。任务有2种执行方式:异步执行和同步执行,他们的区别是,是否会创建新的线程。
同步(sync)和异步(async)的主要区别在于会不会阻塞当前线程,直到block任务执行完毕!
同步操作会阻塞当前线程并等待block中任务执行完毕,然后当前线程才会继续运行。
异步操作则会直接往下执行,不会阻塞当前线程
队列:用于存放任务。分为串行队列和并行队列
串行队列中的任务会根据队列的定义FIFO的执行,一个接一个的先进先出进行执行
并行队列中任务也会FIFO的执行,但是它取出来一个就会放到别的线程,然后再取出来一个又放到另一个线程,这样由于取得动作很快,所以看起来就像所有任务一起执行的。不过GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有的任务同时执行。
同步执行
异步执行
串行队列
当前线程,一个一个执行
其他线程,一个一个执行
并行队列
当前线程,一个一个执行
开很多线程,一起执行
创建队列(线程)
主队列(主线程):这是一个特殊的串行队列。它用于刷新UI,任何需要刷新UI的工作都在主队列执行,所以一般耗时的任务都要放到别的线程执行。
dispatch_queue_t queue = dispatch_get_main_queue();
自己创建的队列:自己可以创建串行队列,并行队列。 其中第一个参数是标识符,用于debug的时候标识唯一队列,可以为空。第二个参数是用于表示创建的队列是串行还是并行,传入 DISPATCH_QUEUE_SERIAL 或者 null表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT表示创建并行队列。
//串行队列 dispatch_queue_t queue1 = dispatch_queue_create("my", NULL); dispatch_queue_t queue2 = dispatch_queue_create("new", DISPATCH_QUEUE_SERIAL); //并行队列 dispatch_queue_t queue3 = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
全局队列:只要是并行任务一般都加到这个队列中,这个系统提供的一个并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建任务:同步任务:会阻塞当前线程(SYNC)
异步任务:不会阻塞当前线程(ASYNC)
dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@",[NSThread currentThread]); });
队列组(线程组):队列组可以将很多队列添加到一个组里面,这样当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
//创建队列组
dispatch_group_t group = dispatch_group_create();
//创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//多次使用队列组的方法执行任务,只有异步方法
dispatch_group_async(group, queue, ^{
for (int i =0; i<3; i++) {
NSLog(@”group1 = %@”,[NSThread currentThread]);
}
});
dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (int i = 0; i<8; i++) { NSLog(@"group2 = %@",[NSThread currentThread]); } }); dispatch_group_async(group, queue, ^{ for (int i = 0; i<5; i++) { NSLog(@"group3 = %@",[NSThread currentThread]); } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 = %@",[NSThread currentThread]); });
关于GCD还有2点:
1. dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
这个方法的传入的queue如果是 DISPATCH_QUEUE_CONCURRENT参数创建的queue时,这个方法会阻塞这个queue(是阻塞queue,而不是当前线程),一直等到这个queue中排在它前面的任务都执行完才会执行自己,自己执行完毕后会取消阻塞,使这个queue中排在它后面的任务继续执行。如果传入的是其他queue则和dispatch_async一样;
2.
dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
这个方法的使用和第一个一样,传入自定义的并发队列DISPATCH_QUEUE_CONCURRENT,它也会阻塞queue,并且还会阻塞当前线程。
相关文章推荐
- 题目1049:字符串去特定字符
- 【cocos2d-x制作别踩白块儿】第一期:游戏介绍
- 2016年,C语言该怎样写
- JVM--垃圾收集器
- 设置 滚动条 宽度为0
- HTML-文本格式化
- 投米变现外挂下载和使用教程独家
- 图像特征提取之(一)HOG特征
- requests上传文件
- 基于Smusic开源播放器的改版播放器 LKMusic正式开工
- Everything常见问题及搜索技巧,附Demo
- WINDOWS的NTP配置
- IE11 SVG对象不支持 parentElement和children
- iOS音频AAC视频H264编码 推流最佳方案
- 【设计原则】面向对象编程的六大原则
- android studio 获取SHA1
- 是否包含,listview中是否有这些数据,如果有的话显示
- [poj 2676] Sudoku DFS
- angularjs 备忘
- 题目1046:求最大值