您的位置:首页 > 移动开发 > IOS开发

iOS之多线程

2016-05-06 20:12 387 查看
程序:由源代码生成的可执行应用

进程:一个正在运行的程序可以看做一个进程,进程拥有独立运行所需的全部资源

线程:程序中独立运行的代码段

一个进程是由一个或多个线程组成,进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行

单线程:

每个正在运行的程序(进程),至少包含一个线程,这个线程叫主线程

主线程在程序启动时被创建,用于执行main函数

只有一个主线程的程序,称作单线程程序

在但线程程序中,主线程负责执行程序的所有代码(UI展现,刷新,网络请求,本地存储等等),这些代码只能顺序执行,无法并发执行

多线程:

拥有多个线程的程序,称作多线程程序

iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程

可以根据需要开辟若干个子线程

子线程和主线程都是独立的运行单元,各自的执行互不影响,因此能够并发执行(伪并发执行,是cpu在多个线程快速的跳跃)

主线程还被称为UI线程,数据的处理放在子线程中,UI的刷新放在主线程中

使用多线程的优点 : 资源利用率更好,程序设计在某些情况下更简单,程序响应更快

使用多线程的缺点 : 多线程尽管提升了性能但是存在一些访问限制,比如线程同步,线程互斥等; 多线程在使用用的时候, 最终要回到主线程刷新UI的, 如果开辟过多的多线程, 会造成CPU的消耗

单线程和多线程的区别:

单线程程序:只有一个线程,即主线程,代码顺序执行,容易出现代码阻塞(页面假死)

多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能

注意:iOS中关于UI的添加和刷新必须在主线程中操作,是为了防止多个线程同时操作UI

iOS平台下的多线程

NSThread

NSThread是一个轻量级的多线程,它有两种创建方式

第一种:初始化方式

- (void)threadMethod {
//初始化tread 当线程是我们手动开辟的时候,需要我们自己来管理内存
//Target在谁里面实现填谁
//object : 回调方法的参数
myThread = [[NSThread alloc] initWithTarget:self selector:@selector(forAction) object:nil];
//为开辟的子线程命名
myThread.name = @"first";
//如果是使用初始化的方式创建的子线程,那么需要我们手动开启线程
[myThread start];
//取消当前线程 取消线程之后,子线程中的代码照样会执行,我们需要在合适的地方使用[myThread canceled]来判断当前线程是否被取消
//    [myThread cancel];
}


- 第二种:遍历构造器


//for循环按钮
- (void)forAction {
//当代码片段是在我们自己手动开辟的线程中执行的,需要我们手动来管理内存,这个时候,我们需要将代码片段放在自动释放池中
@autoreleasepool {
//        for (int i = 0; i < 100; i++) {
//            //线程被取消,但是代码还在执行,所以需要进行以下的判断
//            if (myThread.cancelled) {
//                break;
//            }
//            NSLog(@"*******%@",[NSThread currentThread]);
//        }
NSLog(@"我是子线程---%@",[NSThread currentThread]);
//通过NSObject提供的方法回主线程
//withObject:回调方法的参数
//waitUntilDone:如果是YES,那么就会回调方法中所有的代码执行完成了,才会执行当前线程剩余的代码,如果为NO,那么就不会等待回调方法中的代码执行完成
//如果底下剩余代码跟这行代码有没有依赖关系
[self performSelectorOnMainThread:@selector(backMainThread) withObject:nil waitUntilDone:NO];
NSLog(@"疯狂的萨克斯");
}
}


常用方法

//获取当前线程
[NSThread currentThread];
//获取主线程
[NSThread mainThread];
//线程休眠2秒
[NSThread sleepForTimeInterval:2];


NSThread需要注意的有一下几点

每个线程都维护找与自己对应的NSAutoreleasePool对象,将其放在线程栈的栈顶,当线程结束时,会清空自动释放池

为保证对象的及时释放,在多线程方法中需要添加自动释放池

在应用程序打开的时候,系统会为主线程创建一个自动释放池

我们手动创建的子线程需要我们手动添加自动释放池

NSOperation和NSOperationQueue

NSOperation

NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类

因为它是抽象的,不能够直接使用这个类,而是使用子类(NSInvocationOperation和NSBlockOperation)来执行实际任务

NSOperation(含子类),只是一个操作,本身无主线程,子线程之分,可在任意线程中使用,通常与NSOperationQueue结合使用

NSInvocationOperation

NSInvocationOperation是NSOperation的子类

封装了执行操作的target和要执行的action

//operation的学习,operation本身跟线程无关,只是一个任务操作
- (void)operationStudy {
//target - action方式创建一个任务
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printAction) object:nil];
//如果任务不在队列中,那么就需要我们手动启动任务
[invocationOperation start];
}
//任务的回调方法
- (void)printAction {
NSLog(@"当前线程为%@",[NSThread currentThread]);
}


NSBlockOperation

NSBlockOperation是NSOperation的子类

封装了需要执行的代码块

- (void)blockOperationStudy {
//block的方式创建一个任务
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程是:%@,是否主线程:%d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
}];

for (int i = 0; i < 5; i++) {
//当使用addExecutionBlock为当前任务添加额外的block的时候,那么此block所在的线程就是在当前线程或者其他线程
[blockOperation addExecutionBlock:^{
NSLog(@"excu-----%d-----%@",i,[NSThread currentThread]);
}];
}
//收尾
blockOperation.completionBlock = ^(){
NSLog(@"我就想看结果");
};
//开始执行
[blockOperation start];
}


NSOperationQueue

NSOperationQueue是操作队列,用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行

其中NSOperation可以调节它在队列中的优先级(使用addDependency:设置依赖关系)

当最大并发数设置为1的时候,能实现线程同步(串行执行)

//NSOperationQueue学习,操作队列,实现多线程的一种技术手段,thread需要手动管理内存,队列不需要,thread每次只能开辟一个子线程,队列可以调度,分配,管理多个线程

- (void)operationQueueStudy {
//创建一个其他队列,通过初始化的方式创建的队列只能是其他队列
NSOperationQueue *otherOperationQueue = [[NSOperationQueue alloc] init];
//队列中只能添加任务
//创建一个任务
NSBlockOperation *blockOperation_1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前所在线程1%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation_2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前所在线程2%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation_3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前所在线程3%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation_4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前所在线程4%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation_5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前所在线程5%@",[NSThread currentThread]);
}];
//希望某几个任务执行的时候有先后关系,那么我们就可以为任务添加依赖,不能互相添加为依赖关系
[blockOperation_1 addDependency:blockOperation_5];

//通过设置最大并发数,让一组任务可以有序的执行,最大并发数的默认值为-1,意思为无限
//    otherOperationQueue.maxConcurrentOperationCount = 1;

//将任务添加到队列,在队列中的任务不需要手动启动
[otherOperationQueue addOperation:blockOperation_1];
[otherOperationQueue addOperation:blockOperation_2];
[otherOperationQueue addOperation:blockOperation_3];
[otherOperationQueue addOperation:blockOperation_4];
[otherOperationQueue addOperation:blockOperation_5];

//主队列
[self mainOperationQueue];
}


#pragma mark - operationQueue主队列
//主队列的作用:返回主线程
- (void)mainOperationQueue {
NSLog(@"当前线程为----------------%@",[NSThread currentThread]);
//先获取主队列,在主队列的任务都是在主线程中执行的,其他队列中的任务都是在子线程中执行
NSOperationQueue *myMainQueue = [NSOperationQueue mainQueue];
//添加任务
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-------1%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-------2%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-------3%@",[NSThread currentThread]);
}];
//最大并发数
myMainQueue.maxConcurrentOperationCount = 1;
//把任务添加到主队列中
[myMainQueue addOperation:blockOperation1];
[myMainQueue addOperation:blockOperation2];
[myMainQueue addOperation:blockOperation3];
}


GCD(Grand Central Dispatch)

Grand Central Dispatch是Apple开发的一种多核编程技术.主要用于优化应用程序以支持多核处理器以及其他对称多处理系统

GCD提供函数实现多线程开发,性能更高,功能更强大

它首次发布在Mac OS X 10.6, iOS 4及以上也可用

任务:具有一定功能的代码段. 一般是一个block或者函数

分发队列:GCD以队列的方式进行工作,遵守FIFO

GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务

dispatch queue分为以下两种

SerialQueue: 一次只执行一个任务.Serial Queue通常用于同步访问特定的资源或数据.当你创建多个serial Queue时,虽然它们各自是同步执行的,但Serial Queue与Serial Queue之间是并发执行的,Serial Queue能实现线程同步

//gcd的串行队列  有序的执行一组任务
- (void)serialQueue {
//创建一个串行队列
//第一个参数:当前队列的标签
//第二个参数:当前队列的类型
dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
//创建任务
//第一个参数:当前任务所在的队列
//第二个参数:任务的block回调,逻辑代码在block中
dispatch_async(serialQueue, ^{
NSLog(@"马克打鸟窝");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"抓到鸟蛋烤着吃");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"一个不够吃两个");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"两个不够继续打");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"鸟蛋没了");
NSLog(@"---------%@",[NSThread currentThread]);
});
}


Concurrent: 可以并发地执行多个任务,但是遵守FIFO

第一种方式

//gcd的并行队列
- (void)concurrent {
dispatch_queue_t concurrentQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);
//通过回调函数的方式来实现任务
//第二个参数:回调函数的参数
dispatch_async_f(concurrentQueue, @"赵薇和乐乐开始愉快的生活", function);

dispatch_async(concurrentQueue, ^{
NSLog(@"赵薇和乐乐睡了");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"赵薇哭,乐乐郁闷的抽烟");
NSLog(@"---------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"赵薇去了泰国,为了真爱");
NSLog(@"---------%@",[NSThread currentThread]);
});
}

//声明一个函数
void function(void* content) {
NSLog(@"%@",content);
NSLog(@"---------%@",[NSThread currentThread]);
}


第二种方式

//在实际使用中我们常用GCD的方式:
- (void)usefulMethod {
//系统提供的一个全局队列
//第一个参数:全局队列的优先级
//第二个参数:预留参数,当前并没有用
dispatch_queue_t oneQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(oneQueue, ^{
//在此处执行耗时的操作.......
//当操作执行完成之后我们需要回到主线程刷新UI,gcd回主线程的方式
dispatch_async(dispatch_get_main_queue(), ^{
//已经回到主线程,在此处刷新UI
});
});
}


GCD功能

- (void)otherFunctionsOfGCD {
//    dispatch_group_t 主要用于把一些不想关的任务归为一组
//    组里面放的是队列
//    dispatch_group_async 作用是往组里面的队列添加任务
//    dispatch_group_notify 作用: 监听组里面的任务,等到组里面的任务全部执行完成之后,才会执行其他任务
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.创建组
dispatch_group_t group = dispatch_group_create();
//3.往组里面的队列添加任务(注意:在执行notify之前最起码要向队列中放置一个任务才可以,否则,notify里面的任务不会等待小组里面的其他任务执行完成才执行)
dispatch_group_async(group, queue, ^{
NSLog(@"这是第一个任务,线程是:%@, 是否主线程%d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"这是最后一个任务,组里面的其他任务都执行完毕之后,我就会执行");
});
}


- (void)sqliteAndGCD {
//数据库的读取   可以并发执行通过GCD里面的并行队列去实现
//数据库的写入    只能串行执行,通过GCD里面的串行队列去实现
//但是真正的项目,肯定是既有数据的读取,也有数据库的写入,如何解决这个问题: dispatch_barrier_async 在它之前的任务可以并发去执行,再他之后的任务也可以并发去执行
dispatch_queue_t queue = dispatch_queue_create("concurrentTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"这是第一个读取数据的任务 线程是:%@, 是否主线程: %d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
});

dispatch_barrier_async(queue, ^{
NSLog(@"哥哥我正在给数据库写东西");
});

dispatch_async(queue, ^{
NSLog(@"这是第二个读取数据的任务 线程是: %@, 是否主线程: %d",[NSThread currentThread],[[NSThread currentThread] isMainThread]);
});
}


//dispatch_once : 该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用). 它还接收一个希望在应用的声明周期内仅被调度一次的代码块
//dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要诸如@synchronize之类的来防止使用多个线程或者队列时不同步的问题,通常用于单例

//static SharedData *data = nil;
//+ (SharedData*)defaultData {
//    //block中的代码只会执行一次
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        data = [[SharedData alloc] init];
//    });
//    return data;
//}

//async 不等block执行完,就去执行下面的代码
//sync 会等待block执行完成之后 才会执行block外面的代码
- (void)asyncAndSync {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"第一个任务");
});
NSLog(@"呵呵");

dispatch_async(queue, ^{
NSLog(@"第二个任务");
});
NSLog(@"哈哈");
}


线程间通信

线程间的通信分为两种:

主线程进入子线程(前面的方法都可以)

子线程回到主线程

//子线程回到主线程:
//第一种方法:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执⾏耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执⾏UI刷新操作
});
});

//第二种方法:
[self performSelectorOnMainThread:@selector(backMainThread) withObject:nil waitUntilDone:NO];


线程互斥

多线程并行编程中,线程间同步和互斥是一个很有技巧的也很容易出错的额地方

线程间互斥应对的是这种场景

多个线程操作同一个资源(即某个对象), 需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性的操作后, 状态仍然正确.

典型的例子是”售票厅售票应用”. 售票厅剩余100张票, 10个窗口去卖这些票,这10个窗口,就是10条线程, 售票厅就是他们共同操作的资源,其中剩余的100张票就是这个资源的一个状态,线程买票的过程失去递减这个剩余数量的过程.

线程互斥的解决方案

方法一 : @synchronized自动对参数对象加锁,保证临界区内的代码线程安全

方法二 : NSLock

方法三 : NSConditionLock 条件锁 可以设置条件

方法四 : NSRecursiveLock 递归锁 多次调用不会阻塞已获取该锁的线程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: