多线程的几种模型
1.对多线程的理解
多线程在iOS中用的很多,比如每个asi请求,sdwebimage请求,数据请求,等待网络数据处理,多线程/异步就是为了界面流畅,防止假死,
每一个ASI请求就是一个NSOperation
每一个NSUrlConnection也是一个线程
NSThread是创建线程的一个通用的类,比如创建线程,取消,开始等
NSOperation就是一个简单地以任务为向导的多线程模型,目的是为了不懂操作系统,不懂线程的人使用的
GCD类似于NSOperation,是一个blocks版本的线程模型
苹果公司的Cocoa框架共支持三种多线程机制,分别为NSThread、GCD(Grand Central Dispatch)、Cocoa NSOperation。NSThread是官方推荐的线程处理方式,它在处理机制上,需要开发者负责手动管理Thread的生命周期,包括子线程与主线程之间的同步等。线程共享同一应用程序的部分内存空间,它们拥有对数据相同的访问权限。你得协调多个线程 对同一数据的访问,一般做法是在访问之前加锁,这会导致一定的性能开销。在 iOS 中我们可以使用多种形式的 thread。 比其他两个量级轻 需要自己管理线程的生命周期,线程同步。 线程同步对数据的加锁会有一定的系统开销
如果需要让线程同时并行运行多个,可以将线程加入队列(Queue)中,NSOperationQueue类就是一个线程队列管理类,他提供了线程并行、队列的管理。可以认为NSOperationQueue就是一个线程管理器,通过addOperations方法,我们可以一次性把多个(数组形式)线程添加到队列中。同时,NSOperationQueue允许通过setMaxConcurrentOperationCount方法设置队列的并行(同一时间)运行数量
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。该方法在Mac OS X 10.6雪豹中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,它看起来象就其它语言的闭包(Closure)一样,但苹果把它叫做blocks。
2.多线程的几种实现方式
1.pthread 底层C线程库
2.NSThread OC线程库
3.NSOperationQueue 线程池/线程队列
4.Blocks/GCD Blocks模式的 线程池
NSThread:优点:比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步.线程同步对数据的加锁会有一定的系统开销
————1. pthread——————
/**
* 创建一个线程、该函数不会阻塞、线程创建成功不代表线程已经运行。
*
* @param thread 输出参数——带出一个线程ID(线程ID的类型为 pthread_t)
* @param attr 线程的属性、如果穿NULL、表示按系统默认属性创建线程
* @param start_routine 是一个函数指针、它所指向的函数就是即将要创建的线程的执行路径。
* @param arg 函数指针start_routine所指向的函数的实参
*
* @return 0表示创建线程成功、否则失败。
*/
//int pthread_create(pthread_t restrict thread, const pthread_attr_t *restrict attr, void (start_routine)(void ), void *restrict arg);
void* thread1_main(void* arg)
{
int i=0;
while(1)
{
printf(“111”);
}
}
int main(int argc,const char* argv[])
{
pthread_t tid;
int ret=0;
ret = pthread_create(&tid,NULL,thread1_main,NULL);
if(ret != 0)
{
perror(“ pyhread_create err”);
}
int i=0;
while(1)
{
printf(“main i=%d\n”,i++);
}
return 0;
}
———2. NSthread——————
NSThread总共有两种方法来创建线程
1. [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
2. NSThread* myThread=[[NSThread alloc]initWithTarget:self
selector:@selector(doSomething:)
object:nil];
[myThread start];
第一种方式会直接创建线程并且开始运行线程,第二种方法是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息.
PS:不显示创建线程的方法:
用NSObject的类方法performSelectorInBackground:withObject:创建一个线程
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
比如说这里模仿一个图片下载的过程,
-(void)downLoadImage:(NSString*)url
{
NSData* data=[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage* image=[[UIImage alloc]initWithData:data];
if(image == nil)
{}
else{
[self performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];
}
}
-(void)updateUI:(UIImage*)image
{
self.imageView.image=image;
}
-(void)viewDidLoad
{
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(downloadImage:)toTarget:self withObject:kURL];
}
那么图片下载完成之后如何通知主线程更新界面呢
[self performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他的线程,比如用performSelector: onThread: withObject: waitUntilDone:
—————————NSOperation—————————
1. NSOperationQueue(操作队列)是由GCD提供的队列模型的抽象,是一套OC的API
2. NSOperationQueue有两种不同类型的队列:主队列和自定义队列(分别运行在主线程和后台现场上)
3. 队列处理的任务是NSOperation的子类
a.NSInvocationOperation (调用)
b.NSBlockOperation (块)
—————————————————————
MyOperation2 *myOperation2 = [[MyOperation2 alloc] init];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//将操作对象放在操作队列里面去执行,操作就可以和主函数并发执行(但是他们并不在同一个线程里面,这一步操作实际上就是给我们开辟了一个线程)
[operationQueue addOperation:myOperation2];
————————————————————————
MyOperation *myOperation = [[MyOperation alloc] init];
MyOperation2 *myOperation2 = [[MyOperation2 alloc] init];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//把一个操作扔进一个操作队列里面、该操作就会并发执行,分别在两个不同的线程里面和主线程一起并发执行
[operationQueue addOperation:myOperation];
[operationQueue addOperation:myOperation2];
——————————————————————————————
MyOperation *myOperation = [[MyOperation alloc] init];
MyOperation2 *myOperation2 = [[MyOperation2 alloc] init];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//这说明操作队列是可以跨线程执行的
//让操作2依赖于操作1,这俩个操作分别是在不同的线程里面去执行,而且操作2是在操作1执行完了以后才去执行的,(操作1所在线程和主线程并发执行,操作2所在线程和主线程并发执行)
[myOperation2 addDependency:myOperation];
[operationQueue addOperation:myOperation];
[operationQueue addOperation:myOperation2];
————————————————————————————
操作队列的取消
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
MyOperation *operation = [[MyOperation alloc] init];
[operationQueue addOperation:operation];
getchar();
[operation cancel];//相当于把isCancelled的值设为真
NSLog(@"[operation cancel] end");
getchar();
—————————————————————————————
操作队列的暂停
MyOperation1 *op1 = [[MyOperation1 alloc] init];
MyOperation2 *op2 = [[MyOperation2 alloc] init];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[op2 addDependency:op1];
[operationQueue addOperation:op1];
[operationQueue addOperation:op2];
getchar();
//操作队列的暂停(只能暂停还未运行的操作,暂停后还能恢复)
//让操作队列暂停、操作队列会暂停执行下一个操作、正在执行操作不受影响
[operationQueue setSuspended:YES];
NSLog(@"[operationQueue setSuspended:YES]");
getchar();
[operationQueue setSuspended:NO];
—————————————————————————————————
//实例化一个A类,A类里面有一个函数
A *aObj = [[A alloc] init];
//定义一个"函数调用的操作对象"
//调用这个对象里面的一些方法,前面的只能是改写main()方法,而这个可以实现一些别的方法
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:aObj selector:@selector(fun) object:nil];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:invocationOperation];
——————————————————————————————————————————
//AFN:底层用GCD开发,开放的接口是NSOperation
-(void)demoOp2
{
NSInvocationOperation* op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demoOp:) object:@”hello”];
//在子线程中运行(这两个运行时得注释掉一个,不然会因为争夺op而报错)
// [self.myQueue addOperation:op];
//在主线程中运行
[[NSOperationQueue mainQueue]addOperation:op];
}
-(void)demoOp3
{
NSBlockOperation* op1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@”下载图片–%@”,[NSThread currentThread]);
}];
NSBlockOperation* op2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"修饰图片--%@",[NSThread currentThread]);
}];
NSBlockOperation* op3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"保存图片--%@",[NSThread currentThread]);
}];
NSBlockOperation* op4=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"更新图片--%@",[NSThread currentThread]);
}];
//设定执行顺序,Dependency依赖,可能会开多个线程,但是不会太多(不在主线程上运行)
//依赖关系是可以跨队列的!
//循环依赖会造成程序奔溃
[op2 addDependency:op1];
[op3 addDependency:op2];
[op4 addDependency:op3];
//GCD是串行队列,异步任务,只会开一个线程
[self.myQueue addOperation:op1];
[self.myQueue addOperation:op2];
[self.myQueue addOperation:op3];
//所有UI的更新操作都在主线程上进行
[[NSOperationQueue mainQueue] addOperation:op4];
}
-(void)demoOp1
{
//NSBlockOperation* blockOperation=[NSBlockOperation blockOperationWithBlock:^{
//
// NSLog(@”[NSThread currentThread]==%@”,[NSThread currentThread]);
//
//}];
//
// //所有的自定义队列,都是在子线程中运行的
// [self.myQueue addOperation:blockOperation];
//和上面注释掉的等价
//都是异步执行,但是顺序控制不了,可以通过在串行队列里面添加异步任务即可
//新建线程是有开销的
//设置同时并发的最大线程数时(注意是同时并发,如果一个线程已经销毁,那么可以创建一个新线程),如果前一个线程工作完成,但是还没有销毁,会新建线程
//应用场景:在网络开发中,下载工作!(要注意流量,电量),线程开多了,线程开销就大了,占用cpu和内存就多了,自然就耗电了
//开发过程中,要开的线程的数量的问题
//如果是3G,开3个线程
//如果是WIFI,开6个线程
[self.myQueue setMaxConcurrentOperationCount:2];
for (int i=0; i<10; i++)
{
[self.myQueue addOperationWithBlock:^{
NSLog(@"[NSThread currentThread]==%@ i==%d",[NSThread currentThread],i);
}];
}
//在主线程里面执行
// [[NSOperationQueue mainQueue]addOperationWithBlock:^{
//
// NSLog(@”mainQueue [NSThread currentThread]==%@”,[NSThread currentThread]);
//
// }];
}
3.线程间的通信
1.在应用程序主线程中做事情
[self performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];
2.在指定线程中做事情
performSelector: onThread: withObject: waitUntilDone
3.在当前线程中做事情
performSelector: withObject: afterDelay:
4.取消发送给当前线程的某个消息
cancelPreviousPerformRequestsWithTarget:selector:object:
4.多线程互斥同步问题
在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题:
1. @synchronized(id anObject),(最简单的方法) 会自动对参数对象加锁,保证临界区内的代码线程安全
@synchronized(self) {
// 这段代码对其他 @synchronized(self) 都是互斥的
// self 指向同一个对象
}
//do something here
[theLock unlock];
}
NSRecursiveLock,递归锁
NSRecursiveLock,多次调用不会阻塞已获取该锁的线程
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value) {
[theLock lock];
if (value != 0) {
–value;
MyRecursiveFunction(value);
}
[theLock unlock];
}
MyRecursiveFunction(5);
NSConditionLock,条件锁
NSConditionLock,条件锁,可以设置条件
//公共部分
id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
//线程一,生产者
while(true) {
[condLock lockWhenCondition:NO_DATA];
//生产数据
[condLock unlockWithCondition:HAS_DATA];
}
//线程二,消费者
while (true) {
[condLock lockWhenCondition:HAS_DATA
//消费
[condLock unlockWithCondition:NO_DATA];
}
5.NSDistributedLock,分布锁
NSDistributedLock,分布锁,文件方式实现,可以跨进程 用tryLock方法获取锁。 用unlock方法释放锁。 如果一个获取锁的进程在释放锁之前挂了,那么锁就一直得不到释放了,此时可以通过breakLock强行获取锁。
GCD多线程互斥同步问题(阻塞线程的方式去实现同步)
1.串行队列
(1)GCD下的dispatch_queue队列都是FIFO队列,都会按照提交到队列的顺序执行. 只是根据队列的性质,分为
<1>串行队列:用户队列、主线程队列
<2>并行队列.
(2)同步(dispatch_sync)、异步方式(dispatch_async). 配合串行队列和并行队列使用.
同步队列直接提交两个任务就可以. // 串形队列 dispatch_queue_t serilQueue = dispatch_queue_create(“com.quains.myQueue”, 0);
//开始时间
NSDate *startTime = [NSDate date];
__block UIImage *image = nil;
//1.先去网上下载图片
dispatch_async(serilQueue, ^{
//下载图片
});
//2.在主线程展示到界面里
dispatch_async(serilQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
// 在主线程展示
dispatch_async(dispatch_get_main_queue(), ^{
//显示图片
});
//3.清理
dispatch_release(serilQueue);
[image release];
注意:
(1) __block变量分配在栈,retain下,防止被回收.
(2)dispatch要手动create和release.
(3)提交到主线程队列的时候,慎用同步dispatch_sync方法,有可能造成死锁. 因为主线程队列是串行队列,要等队列里的任务一个一个执行.所以提交一个任务到队列,如果用同步方法就会阻塞住主线程,而主线程又要等主线程队列里的任务都执行完才能执行那个刚提交的,所以主线程队列里还有其他的任务的话,但他已经被阻塞住了,没法先完成队列里的其他任务,即,最后一个任务也没机会执行到,于是造成死锁.
(4)提交到串行队列可以用同步方式,也可以用异步方式.
2.并行队列
采用并行队列的时候,可以采用同步的方式把任务提交到队列里去,即可以实现同步的方式
//新建一个队列 dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//记时
NSDate *startTime = [NSDate date];
//加入队列
dispatch_async(concurrentQueue, ^{
__block UIImage *image = nil;
//1.先去网上下载图片
dispatch_sync(concurrentQueue, ^{
//下载图片
});
//2.在主线程展示到界面里
dispatch_sync(dispatch_get_main_queue(), ^{
//显示图片
});
});
两个同步的任务用一个异步的包起来,提交到并行队列里去,即可实现同步的方式.
4.信号量
信号量 和 琐 的作用差不多,可以用来实现同步的方式. 但是信号量通常用在 允许几个线程同时访问一个资源,通过信号量来控制访问的线程个数.
// 信号量初始化为1 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSDate *startTime = [NSDate date];
__block UIImage *image = nil;
//1.先去网上下载图片
dispatch_async(queue, ^{
// wait操作-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 开始下载
// signal操作+1
dispatch_semaphore_signal(semaphore);
});
// 2.等下载好了再在刷新主线程
dispatch_async(dispatch_get_main_queue(), ^{
// wait操作-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//显示图片
// signal操作+1
dispatch_semaphore_signal(semaphore);
});
dispatch_wait会阻塞线程并且检测信号量的值,直到信号量值大于0才会开始往下执行,同时对信号量执行-1操作.
dispatch_signal则是+1操作.
3.后台运行
GCD的另一个用处是可以让程序在后台较长久的运行。在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。
让程序在后台长久运行的示例代码如下:
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
// 在这里加上你需要长久运行的代码
[self endBackgroundUpdateTask];
}
(void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
(void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
什么是异步?
相对于同步来说,单独起一个或者多个线程去处理
异步是一个概念,线程是一种技术,异步就是用线程这种技术实现的,比如界面下载数据,我们启动一个异步任务ASI去网络下载数据,然后异步刷新界面,我们无须等待网络数据下载完成