多线程网络
NSthread
- 创建和启动线程
//创建一个新线程对象,实例方法,返回一个NSThread对象必须调用start方法启动线程 (实例方法)-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)arg:
//创建并启动新线程,不会返回NSThread对象。会直接创建并启动线程(类方法)+(void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)arg:
target对象的selector方法的方法体代表了线程需要完成的任务,相当于把target对象的selector方法转换为线程执行体.
//创建多线程对象
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//启动多线程
[thread start];
//创建并启动多线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
线程的状态
线程的状态会在运行、就绪状态之间切换。
启用线程使用了start方法后,该线程进入就绪状态,系统会为其创建方法调用栈和程序计数器。当系统调度线程后,线程才会进入运行状态。
注意:若想要子线程的start方法后子线程立即开始执行,可以调用[NSThread sleepForTimeInterval:0.01];
从而让子线程立即获得执行。终止子线程
1>线程执行体方法执行完成,线程正常结束。
2>线程执行过程中出现了错误
3>直接调用NSThread类的exit方法来中止当前正在执行的线程。1
2
3
4if ([NSThread currentThread].isCancelled) {
//中止当前正在执行的线程
[NSThread exit];
}
- 线程睡眠
NSThread的两种类方法:
//让当前正在执行的线程暂停到aDate代表的时间,并进入阻塞状态+ (void)sleepUntilDate:(NSDate *)aDate:
//让当前正在执行的线程暂停ti秒,并进入阻塞状态+ (void)sleepForTimeInterval:(NSTimeInterval)ti:
例子:
使用线程下载网络图片:
1 | //创建多线程对象 |
iOS规定只能在UI线程中修改UI控件的属性
- 改变优先级
NSThread中的实例方法和类方法:
+threadPriority
-threadPriority
+setThreadPriority:(double)priority:
-setThreadPriority:(double)priority:
线程同步与线程通信
在多线程中引入同步解决线程安全问题
使用方法: @synchronized (obj) {//同步代码块
}
obj是同步监视器
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕的后,该线程会释放对同步监视器的锁定。
可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用:- 不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步。
- 如果可变类有两种运行环境:单线程和多线程环境,则应该为该可变类提供两种版本-单线程环境中使用线程不安全版本以保证性能,多线程环境中使用线程安全版本。
释放对同步监视器的锁定
任何线程在进入同步代码块之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?- 当前线程的同步代码块执行结束
- 当前代码块在同步代码块中遇到了goto.return终止了该代码块、该方法继续执行时,当前线程会释放同步监视器。
- 当线程在同步代码块中出现了错误,导致该代码块异常结束时,将会释放同步监视器。
同步锁(NSLock)
Foundation提供NSLock,通过显式定义同步锁对象来实现同步。
NSlock是控制多个线程对共享资源进行访问的工具,通常锁提供了对共享资源的独占访问。在实现线程安全的控制中,使用NSlock对象可以显示地加锁,释放锁。使用NSCondition控制线程通信
NSCondition实现了NSLocking协议,因此也可以调用lock,unlock来实现线程同步。也可以让那些已经锁定NSCondition对象却无法继续执行的线程释放NSCondition对象,NSCondition对象也可以唤醒其他处于等待状态的线程。
方法:-wait,知道其他线程调用NSCondition的signal方法或broadcast方法来唤醒该线程。
-(BOOL)waitUntilDate:(NSDate *)limiteout,
参考:http://blog.csdn.net/totogo2010/article/details/8010231
GCD
- Grand Central Dispatch “牛逼的中枢调度器”
- 是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多CPU内核
- 会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
- 程序员只需要告诉GCD该做什么,而不需要写代码
使用2个步骤:
- 定制任务(执行什么操作)
- 将任务添加到队列中(GCD会自动将队列中的任务取出,放到对应的线程中执行)(先进先出,后进后出)
创建队列
队列:把任务放到队列里面,队列先进先出的原则,
串行队列:顺序,一个一个执行(必须一个任务执行完了,才能从队列里面取出下一个任务)main queue用来更新UI
并发队列:同时执行很多个任务(可以同时取出很多个任务,只要有线程去执行)global queue
1>获取全局并发队列:dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2>获取系统主线程关联的串行队列dispatch_queue_t queue = dispatch_get_main_queue();
3>创建串行队列dispatch_queue_t queue = dispatch_queue_create("mmmm.queue", DISPATCH_QUEUE_SERIAL);
4>创建并发队列dispatch_queue_t queue = dispatch_queue_create("mmmm.queue", DISPATCH_QUEUE_CONCURRENT);
同步:dispatch_sync(dispatch_queue_t, dispatch_block_t block);
异步:dispatch_async(dispatch_queue_t, dispatch_block_t block);
同步和异步的区别:
同步:在当前线程中执行,不具备开启新线程的能力
异步:在另一条新线程中执行,具备开启新线程的能力使用GCD下载图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
//耗时操作
//1.url,确定一个网络上的资源文件路径
NSURL *url = [NSURL URLWithString:@"http://p18.qhimg.com/t0144d6a0802f22be4f.jpg"];
//2.通过URL下载对应的网络资源,网络资源传输的都是二进制
NSData *data = [NSData dataWithContentsOfFile:url];
//3.把二进制转成图片
UIImage *image = [UIImage imageWithData:data];
//4.更新UI,在主线程->直接把主线程添加到主队列,就会在主队列执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.iconView.image = image;
});
});多次执行的任务
控制的代码块执行了5次。
` dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t time){NSLog(@"==执行【%lu次】==%@",time,[NSThread currentThread]);
}`
只执行一次的任务
`static dispatch_once_t oneceToken;
dispatch_once(&oneceToken,^{NSLog(@"==执行代码块=="); //线程暂停3秒 [NSThread sleepForTimeInterval:3];
});`
dispatch_once_t控制函数只执行一次
后台运行
进入后台,系统会自动回调系统程序委托的applicationDidEnterBackground:方法。所有应用需要做:
- 释放所有可以释放的内存
- 保存用户数据或状态信息,所有没写入磁盘的文件或信息,在进入后台之前,都应该写入磁盘,因为程序可能在后台被杀死。
- 进入后台时释放内存
如果应用没有启用ARC机制,程序需要在进入后台是,将那些需要释放的资源的引用计数变为0,从而让系统回收这些资源,当应用转入前台,系统需要重新恢复这些资源。
如果应用启用了ARC机制,程序只要在应用进入后台时,将应用那些需要释放的资源的变量赋值为nil即可。当应用转入前台是,系统需要重新恢复这些资源。 - 进入后台时保存状态
1
2
3
4
5//使用NSUserDefault读取系统已经保存的积分
NSNumber* scoreNumber;
if ((scoreNumber = [[NSUserDefaults standardUserDefaults]setInteger:score forKey:@"score"])) {
score = scoreNumber.integerValue;
}
- 请求更多后台时间
为了请求更多后台时间,应该使用如下步骤:- 调用UIApplication对象的beginBackgroundTaskWithExpirationHandler:方法请求获取更多的后台执行时间。
- 调用dispatch_async()方法将指定代码块提交给后台执行
- 后台任务执行完成是,调用UIApplication对象的endBackgroundTask:方法结束后台任务。
使用NSOperation与NSOperationQueue实现多线程
使用步骤:
- 创建NSOperationQueue队列,并为该队列设置相关属性。
- 创建NSOperation子类的对象,并将该对象提交给NSOperationQueue队列,该队列将会按顺序启动每个NSOperation。
MaxConcurrentOperationCount 设置最大并发线程数。
- 使用NSblockOperation下载图片
1
2
3
4
5
6
7
8
9
10
11
12
13//创建NSBlock
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//从网络获取数据
NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:url]];
//将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image!= nil){
//在主线程中updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];
}
else
NSLog(@"下载图片出现错误");
}];
- 定义NSOperation子类
创建NSOperation的子类需要重写一个方法:-(void)main,该方法将作为NSOpQueue完成任务。
面试题
- 什么是多线程
进程:是指在系统中正在运行的一个应用程序。1个进程要想执行任务,必须得有线程。(一个进程至少要有一个线程)特征:独立性,动态性,并发性。
线程:是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程的。
多线程:在一个进程中可以开启多个线程,每条线程可以并发(同时)执行不同的任务。
多线程编程的优点:- 进程间不能共享内存,但线程之间共享内存非常容易
- 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
- iOS提供了多种多线程实现方式,从而简化了iOS的多线程编程。
参考:http://1108038.blog.51cto.com/1098038/420330
进程间通信—>管道(pipe)、信号(signal)、消息队列、共享内存、信号量、套接字;
线程间通信—>信号量、消息、事件event
- iOS中如何实现多线程
- pthread(一套通用的多线程API,使用难度大 C语言(底层))程序员管理,几乎不用
#include <pthread.h>
- NSThread(面向对象,简单易用,可直接操作多线程对象,OC),偶尔使用
- GCD(旨在替代NSThread等线程技术,充分利用设备的多核,C语言)自动管理,经常使用
- NSOperation(基于GCD,比GCD多了一些简单实用的功能,OC)自动管理,经常使用.
- pthread(一套通用的多线程API,使用难度大 C语言(底层))程序员管理,几乎不用
- 线程间通讯的方法
控制器在子线程发送请求给服务器
服务器在主线程刷新UI界面到控制器
还可以使用GCD,主线程和子线程的通信。
dispath_async(dispatch_get_main_queue(),^{ });
网络图片处理问题中怎么解决一个相同的网络地址重复请求的问题?
利用字典(图片地址为key,下载操作为value)多线程安全的几种解决办法及多线程安全怎么控制?
线程安全的概念: 就是在多个线程同时执行的时候,能够保证资源信息的准确性.- 苹果约定,所有程序的更新UI都在主线程进行,也就不会出现多个线程同时改变一个资源。在主线程更新UI,有什么好处?只在主线程更新UI,就不会出现多个线程同时改变同一个UI控件;主线程的优先级最高。也就意味UI的更新优先级高。 会让用户感觉很流畅 .
- 如果要防止资源抢夺,得用synchronized进行加锁保护.
线程同步:多条线程按顺序的执行任务(互斥锁)互斥锁使用格式@synchronized(锁对象){ //需要锁定的代码 }
- 如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认就是安全的)
GCD内部怎么实现的
1> iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的
2> GCD的API全部在libdispatch库中
3> GCD的底层实现主要有Dispatch Queue和Dispatch Source- Dispatch Queue :管理block(操作)
- Dispatch Source :处理事件(比如线程间的通讯)
补充:GCD:Grand Central Dispatch “牛逼的中枢调度器”,自动利用更多CPU内核,自动管理线程的生命周期(创建线程,调度任务,销毁线程).使用2个步骤: - 定制任务(执行什么操作)用block来封装任务
- 将任务添加到队列中(自动将队列中的任务取出,放到对应的线程中执行)(先进先出,后进后出)
GCD和NSoperation区别
1>GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
2>GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺序、设置最大并发数量
3>NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD需要写很多的代码才能实现
4>NSOperationQueue支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
5>GCD的执行速度比NSOperationQueue快
任务之间不太互相依赖:GCD
任务之间有依赖(或者要监听任务的执行情况):NSOperationQueue- Socket的实现原理以及Socket是如何实现通信的?
Socket:称之为套接字,是一种用于网络传输的“工具”。
socket的实现原理:是基于TCP/UDP的(TCP:传输控制协议,是一种面向连接的,安全的,基于IP传输层的协议,三次握手。例如:XMPP等网络聊天)(UDP:传输控制协议,不是面向连接的,不安全的,基于IP传输层的协议,特点:快,只管发,不管收到没有。例如:游戏,QQ视频,红蜘蛛) http协议的实现
HTTP:是一种超文本协议,定义了网络传输的格式(短连接)
如果利用HTTP做聊天,每次都要重新创建连接,因为HTTP是短连接,一次回话后就断开了,如果利用HTTP做聊天,如果聊天特别频繁,会不断的创建连接,消耗资源,性能不好,服务端不会主动给客户端发送请求。runloop定时源和输入源?
1>你创建的程序不需要显示的创建run loop;每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象, 主线程会自行创建并运行run loop
2>Run loop处理的输入事件有两种不同的来源:输入源(input source)(异步)(处理其他线程回到主线程的消息)和定时源(timer source)(同步)(定时检查界面上有没有点击事件,检查主线程的事件)
3>输入源传递异步消息,通常来自于其他线程或者程序。定时源则传递同步消息,在特定时间或者一定的时间间隔发生- NSRunLoop的实现机制,及在多线程中如何使用
NSRunLoop是IOS消息机制的处理模式.1.NSRunLoop的主要作用:控制NSRunLoop里面线程的执行和休眠,在有事情做的时候使当前NSRunLoop控制的线程工作,没有事情做让当前NSRunLoop的控制的线程休眠。
2.NSRunLoop 就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)异步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
3.runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers
- 只有在为你的程序创建次线程的时候,才需要运行run loop。对于程序的主线程而言,run loop是关键部分。Cocoa提供了运行主线程run loop的代码同时也会自动运行run loop。IOS程序UIApplication中的run方法在程序正常启动的时候就会启动run loop。如果你使用xcode提供的模板创建的程序,那你永远不需要自己去启动run loop
- 在多线程中,你需要判断是否需要run loop。如果需要run loop,那么你要负责配置run loop并启动。你不需要在任何情况下都去启动run loop。比如,你使用线程去处理一个预先定义好的耗时极长的任务时,你就可以毋需启动run loop。Run loop只在你要和线程有交互时才需要