iOS开发之多线程编程

iOS开发之多线程编程

1. 多线程简述

1.1什么是多线程? 解决的问题?

  多线程是指,编程中在主线程之外开辟的新线程,用于处理一些耗时的、并发的任务。使用多线程可以避免主线程的阻塞,也对一个线程不容易实现的任务提供了思路。在多线程的知识中也涉及队列,锁等概念。

  在这里科普一下队列的概念,队列:是管理线程的,相当于线程池,能管理线程什么时候执行。队列分为串行队列和并行队列。

  串行队列:队列中的线程按顺序执行(不会同时执行)

  并行队列:队列中的线程会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。

2.1在iOS开发中我们可能会设计下面几种多线程的使用方式:

    (1)pthred多线程(POSIX标准),用在类unix系统上。

    (2)NSThread   是对pthred面相对象封装

    (3)NSOperation/NSOperationQueue   是使用GCD实现的一套Objective-C的API

    (4)GCD(Grand Central Dispatch)   是基于C语言的底层API

      使用较多,因为:1.支持多核心 2.c和block接口,易使用 3.功能强

2.NSThread的使用

2.1.创建方式2种

    //并行执行(多个任务同时执行)
    //创建新的线程(第一种方式) -- 对应一个方法
    //创建之后不会立即执行,需要start
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(taskA) object:nil];
    [thread1 start];

    //创建新的线程(方式2)--创建并立即执行
    [NSThread detachNewThreadSelector:@selector(taskB:) toTarget:self withObject:thread1];

taskA,taskB为线程启动后要执行的方法

2.2.可以使用通知的方式监听线程的结束

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(dealThreadExit) name:NSThreadWillExitNotification object:nil];

NSThreadWillExitNotification是一个系统通知,如果要监听某个线程的结束,可以在线程结束位置post一个通知,再用通知名监听线程结束。

2.3.线程的通信和控制

在下面的代码中,由createThread方法创建两个线程,分别执行taskA和taskB。taskA和taskB分别执行在不同的子线程中,在taskB方法中传入thread1的引用,在taskB运行到一定时刻,控制taskA所在线程结束。taskA所在线程在监听到退出请求后,处理事务然后调用 [NSThread exit]; 退出。

-(void)createThread{//创建之后不会立即执行,需要start
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(taskA) object:nil];
    [thread1 start];

    //创建新的线程(方式2)--创建并立即执行
    [NSThread detachNewThreadSelector:@selector(taskB:) toTarget:self withObject:thread1];
}

-(void)taskA{
    for (int i = 0 ; i<20; i++) {
        if ([[NSThread currentThread]isCancelled]) {
            //退出当前线程
            [NSThread exit];
        }
        NSLog(@"A = %d",i);
        [NSThread sleepForTimeInterval:0.25];
    }
}

-(void)taskB:(NSThread *)thread{
    for (int i = 0 ; i<20; i++) {
        if (i == 10) {
            [thread cancel];
        }
        NSLog(@"B = %d",i);
        [NSThread sleepForTimeInterval:0.25];
    }
}

3.线程的同步和锁

线程的同步概念是指,多个线程对同一内存区域同时进行读写操作,这就回引发线程安全问题。当一个线程对数据的修改还没有完成,另一个线程进行访问的时候就会拿到理论上的旧的数据,从而引发问题。锁就是对这一问题的一个解决方案,给数据或方法加锁,每次只能有一个线程占有资源,就可以避免数据混乱的产生。

@interface ViewController ()
{
    int _num;
    NSLock * _lock;
}
   _lock = [[NSLock alloc]init];//初始化
        [_lock lock];
        _num++;
        NSLog(@"add = %d",_num);
        [_lock unlock];    

4.如何在线程中更新UI

在iOS设计中,子线程是不允许更改UI的,UI的更新必须放在主线程中,这时可以在子线程中加入下面的代码,切换到主线程更新UI。

waitUntilDone:参数是一个BOOL值,意思是在提供方法完成之前,当前线程是否等待。

[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];

5.NSOperation的使用

5.1什么是NSOperation

  NSOperation 和nsthread相似,实现多线程的一种机制,在NSThread做了更高的抽象,加入block比NSThread简单易用. NSOperation 是一个抽象类,我们一般用它的子类:NSInvocationOperation,NSBlockOperation

5.1NSInvocationOperation的使用

    //NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task1) object:nil];
    //注意:创建之后需要执行,执行的时候默认是同步的
    [invocationOperation start];

task1为线程所要执行的方法。

5.2NSBlockOperation的使用

    //NSBlockOperation
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0 ; i<20; i++) {
            NSLog(@"B = %d",i);
        }
    }];

    [blockOperation start];

5.3NSOperationQueue的使用

    //操作队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //加入操作到队列,并行开始并行执行,这个操作讲一直存在在队列中知道被执行完毕  //并行执行,这两个操作将同时执行
    [queue addOperation:invocationOperation];
    [queue addOperation:blockOperation];

6.GCD的使用

6.1线程创建和方法使用

创建两个异步线程

- (void)createAsyncTask {
    //定义一个队列,这里为一个全局队列,第一个参数为队列的优先级,这里为默认优先级
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //params1:传入queue,有三种queue
    //1 main queue UI所在
    //2 global queue 全局队列(工作线程)
    //3 自定义queue
    dispatch_async(queue, ^{
        for (int i = 0 ; i<20; i++) {
            NSLog(@"A = %d",i);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0 ; i<20; i++) {
            NSLog(@"B = %d",i);
        }
    });
}

整个程序仅执行一次的方法,可以线程安全的实现单例模式,下面的方法无论调用几次,都只将打印一次runOnce。

-(void)runOnce{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"runOnce");
    });
}

延时执行的方法,在“5”的位置可以填写延时的时间,单位为秒。

-(void)delayRun{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"heh heh heh");
    });
}

将线程加入一个组,整个组的任务执行完毕后可以执行特定的操作

-(void)groupRun{

    //group
    dispatch_group_t group = dispatch_group_create();

    //add task 7s done
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i<100; i++) {
            NSLog(@"a = %d",i);
            [NSThread sleepForTimeInterval:0.07];
        }
    });

    //add task 5s done
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i<100; i++) {
            NSLog(@"b = %d",i);
            [NSThread sleepForTimeInterval:0.05];
        }
    });

    //add task 6s done
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i<100; i++) {
            NSLog(@"c = %d",i);
            [NSThread sleepForTimeInterval:0.06];
        }
    });

    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"all tasks have done");
    });
}

6.2线程阻塞和执行顺序的讨论

/* 在主队列开启同步任务,为什么会阻塞线程?
 > 在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程,而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用程序。因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了。

 */
- (void)main_queue_deadlock
{

    dispatch_queue_t q = dispatch_get_main_queue();

    NSLog(@"1111");

    dispatch_async(q, ^{
        NSLog(@"主队列异步 %@", [NSThread currentThread]);
    });

    NSLog(@"2222");

    // 下面会造成线程死锁
    //    dispatch_sync(q, ^{
    //        NSLog(@"主队列同步 %@", [NSThread currentThread]);
    //    }); 

}
/**
 *  并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序
 */
- (void)concurrent_queue
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@1111111", [NSThread currentThread]);
        dispatch_sync(q, ^{
            NSLog(@"同步任务 %@2222222", [NSThread currentThread]);
        });

        dispatch_sync(q, ^{
            NSLog(@"同步任务 %@333333", [NSThread currentThread]);
        });

    });

    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@444444", [NSThread currentThread]);
    });

    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@555555", [NSThread currentThread]);
    });
    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@666666", [NSThread currentThread]);
    });
}

/**
 *  串行队列开启异步任务,是有顺序的
 */
- (void)serial_queue
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);

    dispatch_async(q, ^{
        NSLog(@"异步任务 %@1111111", [NSThread currentThread]);
    });

    dispatch_async(q, ^{
        NSLog(@"异步任务 %@22222", [NSThread currentThread]);
    });

    dispatch_async(q, ^{
        NSLog(@"异步任务 %@3333", [NSThread currentThread]);
    });
    dispatch_async(q, ^{
        NSLog(@"异步任务 %@44444", [NSThread currentThread]);
    });

}

/**
 *  串行队列开启异步任务后嵌套同步任务造成死锁
 */
- (void)serial_queue_deadlock2
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);

    dispatch_async(q, ^{
        NSLog(@"异步任务 %@", [NSThread currentThread]);
        // 下面开启同步造成死锁:因为串行队列中线程是有执行顺序的,需要等上面开启的异步任务执行完毕,才会执行下面开启的同步任务。而上面的异步任务还没执行完,要到下面的大括号才算执行完毕,而下面的同步任务已经在抢占资源了,就会发生死锁。
        dispatch_sync(q, ^{
            NSLog(@"同步任务 %@", [NSThread currentThread]);
        });

    });

}

/**
 *  串行队列开启同步任务后嵌套同步任务造成死锁
 */
- (void)serial_queue_deadlock1
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@", [NSThread currentThread]);
        // 下面开启同步造成死锁:因为串行队列中线程是有执行顺序的,需要等上面开启的同步任务执行完毕,才会执行下面开启的同步任务。而上面的同步任务还没执行完,要到下面的大括号才算执行完毕,而下面的同步任务已经在抢占资源了,就会发生死锁。
        dispatch_sync(q, ^{
            NSLog(@"同步任务 %@", [NSThread currentThread]);
        });

    });
    NSLog(@"同步任务 %@", [NSThread currentThread]);
}

/**
 *  串行队列开启同步任务后嵌套异步任务不造成死锁
 */
- (void)serial_queue1
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(q, ^{
        NSLog(@"同步任务 %@", [NSThread currentThread]);

        // 开启异步,就会开启一个新的线程,不会阻塞线程
        dispatch_async(q, ^{
            NSLog(@"异步任务 %@", [NSThread currentThread]);
        });

    });
    NSLog(@"同步任务 %@", [NSThread currentThread]);
}
时间: 2024-10-16 09:48:35

iOS开发之多线程编程的相关文章

IOS开发-多线程编程技术(Thread、Cocoa operations、GCD)

前言:在软件开发中,多线程编程技术被广泛应用,相信多线程任务对我们来说已经不再陌生了.有了多线程技术,我们可以同做多个事情,而不是一个一个任务地进行.比如:前端和后台作交互.大任务(需要耗费一定的时间和资源)等等.也就是说,我们可以使用线程把占据时间长的任务放到后台中处理,而不影响到用户的使用. 线程的定义: 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.进程也可能是整个程序或者是部分程序的动态执行.线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行.也可以把它理

iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄

前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传.    在实际开发中,输入输出流用的比较少,但是用起来也是很方便的.iOS开发用到的输入输出流和在Java中的输入输出流是几乎一样的,本质也是一个意思:将网络返回的数据当做流来处理.    输入输出的理解:输入到哪里?输出到哪里?这个问题不难理解,输入输出是要站着服务器角度来思考的,下面用图来解释:    代码关键词: 1.在接收到响应头的代理

iOS开发之网络编程--使用NSURLConnection实现大文件下载

主要思路(实现下载数据分段写入缓存中) 1.使用NSURLConnectionDataDelegate以及代理方法.2.在成功获取响应的代理方法中,获得沙盒全路径,并在该路径下创建空文件和文件句柄.3.在获取data的代理方法中,先设置句柄在沙盒全路径文件末尾,然后通过句柄写入data数据.4.在文件下载完的代理方法中,关闭句柄同时设置句柄引用为nil释放句柄和指针. 使用句柄的思路图(红色的箭头表示句柄,灰色的箭头表示移动的路径): 代码关键词: 类:NSFileHandle的方法 1.fil

iOS开发之网络编程--5、NSURLSessionUploadTask+NSURLSessionDataDelegate代理上传

前言:关于NSURLSession的主要内容快到尾声了,这里就讲讲文件上传.关于文件上传当然就要使用NSURLSessionUploadTask,这里直接讲解常用的会和代理NSURLSessionDataDelegate一起搭配实现文件上传功能.另外,下面使用的文件上传思路是和NSURLConnection中本人之前的随笔<iOS开发之网络编程--使用NSURLConnection实现文件上传>提到的上传思路是一样的,都是要将请求信息拼接起来,然后传入到请求里进行上传.这个拼接过程是必要的,但

iOS开发之网络编程--2、NSURLSessionDownloadTask文件下载

本文内容大纲: 1.回顾NSURLSessionTask 2.NSURLSessionDownloadTask大文件之block下载 3.NSURLSessionDownloadTask大文件之代理方法下载 4.NSURLSessionDownloadTask大文件之代理方法实现断点续传下载 前言:如果读者是第一次阅读或者是学习关于本篇要介绍的NSURLSession的知识,最好先阅读本人前篇<iOS开发之网络编程--1.NSURLSession的基本使用>然后再学习本篇比较好. 1.回顾NS

iOS开发之动画编程的几种方法

iOS开发之动画编程的几种方法 IOS中的动画总结来说有五种:UIView<block>,CAAnimation<CABasicAnimation,CATransition,CAKeyframeAnimation>,NSTimer 这里我就总结了一下这五种方法,其实iOS开发中动画的编程都会在这里面变化,所以只要弄懂了这些动画编程就不难了. 一:UIView动画 一般方式 [UIView beginAnimations:@"ddd" context:nil];/

iOS开发之多线程——GCD介绍

iOS开发之多线程——GCD的介绍 一.简单介绍 1.GCD ( Grand Central Dispatch) 可以翻译为“中枢调度器”.纯C语言,并且提供了非常强大的函数. 2.GCD 有什么优势: GCD是苹果公司为多核的并行运算提出的解决方案 GCD会自动利用更多地CPU 内核 (比如双核.四核) GCD会自动管理线程的生命周期 (创建线程.调度任务.销毁线程) 程序猿只需要告诉GCD想要执行设呢任务,不需要编写任何线程管理代码. 二.任务和队列 GCD中有两个核心概念 (1)任务: 执

IOS开发之网络编程(请求数据和断点续传)

IOS开发中网络编程应用场景:JSON数据获取,网络数据的下载. 一:请求JSON数据一般用异步请求的方式,如果用同步请求,则会造成IOS界面的执行过程阻塞,即界面部分在请求数据的过程中必须等待数据加载完毕. JSON数据的获取步骤: 1.设置网络地址的字符串:NSString *URLString = @"http://www.baidu.com"; 2.创建URL:NSURL *URL = [NSURL URLWithString:URLString]; 3.创建请求:NSURLR

学习IOS开发网络多线程篇--NSThread/GCD/

NSThread:利用NSThread创建和启用一个线程 1. NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];,调用后调用[thread start]; 2. 创建线程后自动启动线程 ,[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; 3. 隐式创建