笔记-多线程

2017/02/20
今天的目标只有一个:弄懂多线程(真心的)

一、认识多线程

进程:

在系统中正在运行的一个应用程序;

线程:

一个进程想要执行任务,必须得有线程(每一个进程至少有一条线程),线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。

多线程:

一个进程中可以开启多条线程,每条线程可以并行执行不同的任务(比如:进程->车间,线程->车间工人)
(1)每一个程序都有一个主线程,程序启动时创建(调用main来启动)
(2)主线程的生命周期是和应用程序绑定的,程序退出(结束)时,主线程也就停止了
(3)多线程技术表示,一个应用程序有多个线程,使用多线程能提供CPU的使用率,防止主线程的堵塞
(4)任何有可能堵塞主线程的任务不要在主线程执行(访问网络)

二、多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

思考:如果线程非常非常多,会发生什么情况?

CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

三、多线程的优缺点

优点:
  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU、内存的利用率)
缺点:
  • 开启线程需要占用一定的内存空间(默认情况下,主线程占用了1M,子线程占用512K),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU在调度线程上的开销就越大
  • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享

四、多线程在iOS开发中的应用

主线程:一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”
主线程的主要作用:

  • 显示/刷新UI界面
  • 处理UI事件(比如:点击事件、滚动事件、拖拽事件等)

主线程的使用注意:别将比较耗时操作放到主线程中。
耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验

使用场合:
  • 耗时操作,例如:网络图片、视频、歌曲、书籍等资源下载
  • 游戏中的声音播放

五、iOS的三种多线程技术

1、NSThread:

优点:NSThread比其他两个轻量级,使用简单
缺点:不能控制线程的执行顺序(需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁对会有一定的系统开销)【需要使用start方法,才能启动实例化出来的线程、控制并发线程数、先后顺序困难,例如:下载图片(后台线程)->滤镜美化(后台线程)->更新UI(主线程)】

  @implementation ViewController

  - (void)viewDidLoad {
      [super viewDidLoad];

      for(int i=1; i<100; i++) {
         NSLog(@"=====%@======%d",[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil].name,i);
         if (i == 7) {
         //创建线程对象
         NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
         //启动新线程
         [thread start];
        /**
         *  创建并启动新线程
         */
        //[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
        /**
         * 上面两种方法本质上都是将target对象的selector方法转换为线程执行体,其中selector方法最多可以接收一个参数,而argument就是代表传给selector方法的参数。这两种创建新线程的方式并没有明显的区别,只是上面的是一个实例化方法,该方法返回一个thread对象,调用start方法开启线程。下面的不会返回NSThread对象,因此这种方法会直接创建并启动新线程。
         *
         */
    }
   }
   /**
       *  每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的则反之。每个子线程默认的优先级是0.5. NSThread 通过如下代码演示优先级。
    */
  //    NSLog(@"UI线程的优先级为:%g",[NSThread threadPriority]);
  //    //创建第一个线程对象
  //    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  //    //设置第一个线程对象的名字
  //    thread1.name = @"线程A";
  //    NSLog(@"线程A的优先级为:%g",thread1.threadPriority);
  //    //设置使用最低优先级
  //    thread1.threadPriority = 0.0;
  //    //创建第二个线程对象
  //    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  //    //设置第二个线程对象的名字
  //    thread2.name = @"线程B";
  //    NSLog(@"线程B的优先级为:%g",thread2.threadPriority);
  //    //设置使用最高优先级
  //    thread2.threadPriority = 1.0;
  //    //启动两个线程
  //    [thread1 start];
  //    [thread2 start];
  }
  - (void)run {
       for (int i=0; i<100; i++) {
    //        if ([NSThread currentThread].isCancelled) {
    //            //终止当前正在执行的线程
    //            [NSThread exit];
    //        }
     NSLog(@"------%@-----%d",[NSThread currentThread].name,i);
    //没执行一次,线程暂停0.5秒
    //        [NSThread sleepForTimeInterval:0.5];
    
    //使当前线程暂停一段时间,或者暂停到某个时刻
    //        + (void)sleepForTimeInterval:(NSTimeInterval)time;
    //        + (void)sleepUntilDate:(NSDate *)date;
   }
  }
   //- (IBAction)cancelThread:(id)sender {
   //    //取消thread线程,调用该方法,调用该方法后,thread的isCancelled方法将会返回NO
   //    [thread cancel];
   //}
  @end

相关代码下载:OC-NSThread

2.NSOperation和NSOperationQueue

NSOperation是一个抽象基类,基本没有什么实际使用价值。我们使用最多的是系统封装好的NSInvocationOperation和NSBlockOperation。

(1)NSOperation

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:

  • 将要执行的任务封装到一个 NSOperation 对象中。
  • 将此任务添加到一个 NSOperationQueue 对象中。
NSOperation * operation = [[NSOperation alloc]init];
//开始执行
[operation start];
//取消执行
[operation cancel];
//执行结束后调用的Block
[operation setCompletionBlock:^{
    NSLog(@"执行结束");
}];

NSInvocationOperation的使用方式和给Button添加事件比较相似,需要一个对象和一个Selector。使用方法非常简单。

添加任务

值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

//创建
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
//执行
[invo start];

 - (void)testNSOperation {
    NSLog(@"我在第%@个线程",[NSThread currentThread]);
 }

我们可以看到NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西也没有什么卵用,它需要配合我们后面介绍的NSOperationQueue去使用才能实现多线程调用,所以这里我们只需要记住有这么一个东西就行了。参考文章:NSOperation

(2)NSBlockOperation
//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];

 //2.开始任务
 [operation start];

参考:NSBlockOperation

3.GCD

Grand Central Dispatch,听名字就霸气。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。

首先来了解两个基本概念:任务和队列
  • 任务:及操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是 是否会创建新的线程。

     同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!
     如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
     如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
    
  • 队列:用于存放任务。一共有两种队列,串行队列和并行队列(先入先出)。

口诀:

  • 同步不开异步开,串行开一条,并行开多条
(1)创建队列

主队列:
这是一个特殊的 串行队列。什么是主队列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。

  dispatch_queue_t queue = ispatch_get_main_queue();

自己创建的队列:
第一个参数是标识符,第二个参数用来表示创建的对垒是串行还是并行。

//创建串行队列 - DISPATCH_QUEUE_SERIAL或NULL
serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
//创建并发队列 - DISPATCH_QUEUE_CONCURRENT
concurrentQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_CONCURRENT);

全局并发队列:
只要是并行任务一般都加入到这个队列。这是系统提供的一个并发队列。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

(2)创建任务

  • 同步任务:会阻塞当前线程(sync)
    //将代码块以同步方式提交给指定队列,该队列底层的线程池将负责执行该代码块
    dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
    //将函数以同步方式提交给指定队列,该队列底层的线程池将负责执行该函数
    dispatch_sync_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)

  • 异步任务:不会阻塞当前线程 (async)

     //将代码块以异步方式提交给指定队列,该队列底层的线程池将负责执行该代码块
     dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
     //将函数以异步方式提交给指定队列,该队列底层的线程池将负责执行该函数
     dispatch_async_f(<#dispatch_queue_t queue#>, <#void *context#>, <#dispatch_function_t work#>)
    

相关代码下载:OC-GCD

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容