我们介绍多线程首先我们需要一些基础知识一下我们一一介绍
进程:指在系统中正在运行的一个应用程序,每个进程是独立,每个京城都运行在其专用受保护的内存空间。这也就是说多进程要比多线程健壮我们之后介绍。
线程:是进程的基本执行单元,一个进程的所有任务都在线程中执行,在一个线程中任务都是串行(顺序执行)。
线程通信:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信,比如一个线程传递数据给另一个线程,执行完一个线程的任务后在执行另一个线程。
多线程:一个进程中开启多条线程,每条线程并发(同时)执行不同的任务,多线程可以提高程序的执行效率。
原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。同样如果频繁的切换线程,使得CPU在N个线程之间调度,消耗大量的的CPU资源使得线程的执行效率降低,多线程开发我们要找到平衡点,控制线程的数量。
优点
提高程序的执行效率,不会因为一些耗时操作影响其他功能正常运行同时还可以提高资源利用率,CPU和内存的利用率。
缺点
开启线程会占用一定的内存空间,如果大量的开启线程会占用大量的内存空间,同时CPU在调度线程上也会占用资源,在程序设计会变得复杂,线程的通信和数据的共享等等。
iOS的多线程的实现目前有4种:Pthread, NSThread, GCD, NSOperation,以下我们分别介绍。
Pthread是一套通用的API,多个平台支持,它是基于C语言的,线程的生命周期需要我们手动管理,使用难度大,平时开发基本不用。我们还是来一个简单的例子
//创建一个线程并自动执行 pthread_create(&thread, NULL, start, NULL);NSThread是Objective-C对C语言Pthread的封装,面向对象简单易用,可直接操作线程对象,同样生命周期需要我们管理。开发中不常使用,以下是对NSThread方法得说明。
创建、启动线程NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];[thread start];主线程相关用法+ (NSThread *)mainThread; // 获得主线程- (BOOL)isMainThread; // 是否为主线程+ (BOOL)isMainThread; // 是否为主线程获得当前线程 NSThread *current = [NSThread currentThread];线程的名字- (void)setName:(NSString *)n;- (NSString *)name;线程的调度优先级,调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高+ (double)threadPRiority;+ (BOOL)setThreadPriority:(double)p;- (double)threadPriority;- (BOOL)setThreadPriority:(double)p;创建线程后自动启动线程[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];隐式创建并启动线程[self performSelectorInBackground:@selector(test) withObject:nil];控制线程状态//启动线程- (void)start; //阻塞(暂停)线程+ (void)sleepUntilDate:(NSDate *)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;//强制停止线程+ (void)exit;//线程停止了,就不能再次开启任务-线程间通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;GCD全称是Grand Central Dispatch,苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),会自动管理线程的生命周期(创建线程、调度任务、销毁线程),虽然是纯C语言,基于Block实现,但是使用起来非常方便。想要更深的了解GCD就必须知道任务和队列。
任务:就是需要执行的操作,我们可以定制任务,任务的执行分为同步执行和异步执行,区别在于是否开启新的线程。
同步执行
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);异步执行
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);队列:就是用来存放任务,把我们定制的任务存放在队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列FIFO原则:先进先出,后进后出。队列分为两大类型:并发队列和串行队列。
并发队列 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效,GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, // 队列的优先级unsigned long flags); // 此参数暂时无用,用0即可dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列/*全局并发队列的优先级#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台*/串行队列 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务),GCD中获得串行有2种
1.使用dispatch_queue_create函数创建串行队列
dispatch_queue_tdispatch_queue_create(const char *label, // 队列名称 dispatch_queue_attr_t attr); // 队列属性,一般用NULL即可dispatch_queue_t queue = dispatch_queue_create("first", NULL); // 创建dispatch_release(queue); // 非ARC需要释放手动创建的队列2.使用主队列(跟主线程相关联的队列),是GCD自带的一种特殊的串行队列,主队列的任务都会放在主线程中执行,使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();我们对任务同步异步和队列串行并发的执行分析
串行队列同步执行:不开线程,在原来的线程顺序执行dispatch_queue_t queue = dispatch_queue_create("cs",DISPATCH_QUEUE_SERIAL);dispatch_sync(queue,^{ //串行队列,同步执行,不开线程,顺序执行});串行队列异步执行:开一条线程,在线程中顺序执行,效率低占用资源少(相对比较)dispatch_queue_t queue = dispatch_queue_create("cs",DISPATCH_QUEUE_SERIAL);dispatch_async(queue,^{ //串行队列,异步执行,开一条线程,顺序执行});并发队列同步执行:不开线程,同步执行//并发队列dispatch_queue_t queue = dispatch_queue_create("bs",DISPATHCH_QUEUE_CONCURRENT);dispatch_sync(queue,^{//先执行同步任务,不开线程});dispatch_async(queue,^{//等到同步任务执行完成后,开启线程执行当前操作});dispatch_async(queue,^{//等到同步任务执行完成后,开启线程执行当前操作});并发队列异步执行:开第一个线程,并发执行,效率高占用资源大//并发队列dispatch_queue_t queue = dispatch_queue_create("ba",DISPATHCH_QUEUE_CONCURRENT);dispatch_async(queue,^{//开启线程执行当前操作});dispatch_async(queue,^{//开启线程执行当前操作});1.调用NSObject方法
//2秒后调用[self performSelector:@selector(run) withObject:nil afterDelay:2.0];2.使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2秒后异步执行这里的代码...});使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ // 只执行1次的代码(这里面默认是线程安全的)});可以管理组内的队列执行情况,方便对线程管理,比如我们异步执行两个耗时的操作,等待完成后回到主线程执行操作
dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行1个耗时的异步操作});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行1个耗时的异步操作});dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的两个异步操作都执行完毕后,回到主线程...});NSOperation:是基于GCD实现的,比GCD多了更加简单实用的功能,更加面向对象。配合使用NSOperation和NSOperationQueue实现多线程编程,我们开发中经常使用。
提供两种队列:主队列和全局并发队列
主队列
NSOperationQueue *queue = [[NSOperationQueue mainQueue];全局并发队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];添加任务
//1.添加一个NSOperation的子类- (void); addOperation:(NSOperation *)op[queue addOperation:op];//2.添加一个Block- (void)addOperationWithBlock:(void (^)(void))block;[queue addOperationWithBlock:^{ //代码}];NSOperation是个抽象类,我们使用必须使用它的子类,使用NSOperation子类的方式有3种
1. NSInvocationOperation 对GCD中全局并发队列的封装
//1.创建操作NSOperation *op = [[NSInvocationOperation alloc] initWithTatger:self selecter:@Selector(downloadImage:) object:@"Invocation"];//2. 加入队列中NSOperationQueue *queue = [[NSOperationQueue alloc]init];//异步执行[queue addOperation:op];2. NSBlockOperation对GCD中全局并发队列的封装
NSOperationQueue *queue = [[NSOperationQueue alloc]init];//添加任务NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ //需要执行的操作}];//添加到队列[queue addOperation:op];3. 自定义子类继承NSOperation,实现内部相应的方法
最大并发数就是同时执行的任务数,可以使用下面的方法
- (NSInteger)maxConcurrentOperationCount;- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;取消
- (void)cancelAllOperations;//也可以调用NSOperation的- (void)cancel方法取消单个操作暂停/恢复
// YES代表暂停队列,NO代表恢复队列- (void)setSuspended:(BOOL)b; - (BOOL)isSuspended;设置NSOperation在queue中的优先级,可以改变操作的执行优先级
- (NSOperationQueuePriority)queuePriority;- (void)setQueuePriority:(NSOperationQueuePriority)p;/* 优先级的取值 NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8*/可以监听一个操作的执行完毕
- (void (^)(void))completionBlock;- (void)setCompletionBlock:(void (^)(void))block;NSOperation之间可以设置依赖来保证执行顺序,比如一定要让操作A执行完后,才能执行操作B
// 操作B依赖于操作A[operationB addDependency:operationA];我们之前提到的原子性和非原子性,相信在这次的介绍中已经有根深的理解了
1.资源共享:一个资源可能被多个线程共享,多线程访问同一块资源容易引发数据错乱和数据安全
用来防止两个进程或线程在同一时刻访问相同的共享资源。
@synchronized(锁对象) { // 需要锁定的代码 }优点:能有效防止因多线程抢夺资源造成的数据安全问题 缺点:需要消耗大量的CPU资源
死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。(在学校每次考试都考)
今天写了好长时间,总结整理,眼睛感觉已经不是自己的了,发现有不对的地方欢迎评论和联系微博
新闻热点
疑难解答