ios多线程

  • 什么是进程

  • 进程是指在系统中正在运行的一个应用程序

  • 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

  • 比如同时打开迅雷、Xcode,系统就会分别启动2个进程

  • 什么是线程

  • 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)

  • 一个进程(程序)的所有任务都在线程中执行

  • 比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行

  • 1个线程中任务的执行是串行的

  • 如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务

  • 也就是说,在同一时间内,1个线程只能执行1个任务

什么是多线程
  • 1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
  • 多线程技术可以提高程序的执行效率
  • 比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C
多线程的原理
  • 同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
  • 多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
  • 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优点
  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU、内存利用率)
多线程的缺点
  • 创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
  • 如果开启大量的线程,会降低程序的性能
  • 线程越多,CPU在调度线程上的开销就越大
  • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享
什么是主线程
  • 一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”

  • 主线程的主要作用

    • 显示\刷新UI界面
    • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
  • 主线程的使用注意

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

在主线程放一个需要耗时10秒的操作;用户在第五秒的时候点击屏幕按钮,在第十秒的时候才会做出响应;造成卡顿现象;

  • 在ios领域里面真正的多线程技术只有这两个pthread和NSthread;
    GCD不能直接操作多线程;属于并发技术;NSOperation也是一样,是操作队列的,与线程无关,线程部分GCD已经帮你封装好了!
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
  • 苹果不建议开发者使用多线程技术;
    鼓励使用并发技术;
NSThread
  • 创建线程的几种方法
// 创建一个NSThread
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//线程就绪-之后等着cpu调度; 
[thread start];

//demo函数在子线程执行
-(void)demo:(id)obj{
    for (int i = 0; i < 2; i++) {
        //number !=1
        NSLog(@"%@",[NSThread currentThread]);
        //[NSThread currentThread] 打印当前线程   
        //返回一个Thread对象,里面有number和name属性
        //number == 1 说明是主线程   number != 1 就是其他线程
    }
}

//detach ==> 分离
//分离出一条子线程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"Detach"];

//InBackground 就是在后台(子线程)运行!!
//是NSObject的分类 意味着所有的继承NSObject的都可以使用这个方法
//非常方便.不用NSThread对象
[self performSelectorInBackground:@selector(demo:) withObject:@"background"];

线程的状态
  • 新建一条线程,线程在可调度线程池里 (由cpu进行调度的)
  • 当 [thread start] 的时候,在可调度线程池里的线程都处于就绪状态
  • cpu调度处于就绪状态的线程
  • 运行状态
  • 线程执行完毕之后线程在可调度线程池取出,干掉;

线程阻塞

  • 当运行满足某个条件,会让线程"睡一会

提示:sleep 方法是类方法,会直接休眠当前线程!!

//当前线程睡两秒
[NSThread sleepForTimeInterval:2.0];
//一旦强行终止线程,后续的所有代码都不会被执行
//注意:在终止线程之前,应该要释放之前分配的对象!!
[NSThread exit];
//创建线程
NSThread * t = [[NSThread alloc]initWithTarget:self selector:@selector(theadStatus) object:nil];
//线程就绪(CPU翻牌)
[t start];

-(void)theadStatus{
    for (int i = 0; i < 20;i++) {
        //阻塞,当运行满足某个条件,会让线程"睡一会"
        //提示:sleep 方法是类方法,会直接休眠当前线程!!
        if (i == 8) {
            NSLog(@"睡一会");    
            //睡的是子线程 
            //注意!!! exit会杀掉主线程!但是APP不会挂掉!!
             [NSThread sleepForTimeInterval:2.0];
        }
        NSLog(@"%@  %d",[NSThread currentThread],i);
        
        //当线程满足某一个条件时,可以强行终止的
        //exit 类方法,哥么终止当前线程!!!!
        if (i == 15) {
            [NSThread exit];  //线程就就处于死亡状态了
        }
    }
    NSLog(@"能来吗???");  //来不了了
}

threadPriority  //线程优先级
  • 优先级只是保证cpu调度的可能性会高!
    用优先级来控制线程得执行顺序是不理性的!
  • 建议:再开发的时候不要修改优先级;
    在多线程的开发中,不要相信一次的运行结果!
多线程的目的
  • 将耗时操作放在后台,不阻塞UI线程

多线程的安全隐患

  • 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

    • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

安全隐患的解决 - 互斥锁
  • 一个线程访问一个资源时,先给他上锁,访问完成之后再解锁让其他线程访问;
  • 互斥锁 -- 保证锁内的代码,同一时间,只有一条线程执行!
  • 互斥锁 的范围 应该尽量小,范围大了 效率就差!!
        //互斥锁
        @synchronized (self) {
            //括号里是 同一时间只能有一条线程执行的代码
        };

好比你去上厕所,你把门锁上了,别人只能在外面等着,等你操作完厕所,别人才能进来;如果你不锁门,两个人在厕所后果不堪设想!😏

原子属性
  • atomic-用来保护线程安全(多线程执行写入操作的时候,保证同一时间只有一个线程执行写入 只对写入操作上锁,读不上锁)
  • 单写多读的原子属性(只能单个线程写,但可以多个线程读)
  • 实际上原子属性内部有一把锁叫 - 自旋锁
    • 自旋锁&互斥锁异同
      • 共同点

        • 都能保证线程安全
      • 不同点

        • 互斥锁:被锁在外面的线程,处于休眠状态;等待锁打开,然后被唤醒;
        • 自旋锁:被锁在外面的线程,用死循环的方式,等待锁打开;
  • 不管什么锁,都很消耗性能,效率不高;

想要模拟原子属性的时候就在set方法里加了一把锁; @synchronized
如果不写nonatomic | atomic 默认是 atomic

线程间的通信方法

  • 就这五个方法
    • 把SEL丢到某一个线程去执行
/**
 waitUntilDone : 当前线程是否等待SEL执行完再继续
*/

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
    // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);


GCD(重点)

GCD并不是多线程技术;属于并发解决技术;

  • GCD是苹果为了适配多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD的使用就2个步骤
  • 定制任务

  • 将任务添加到队列中

GCD中有2个核心概念
  • 任务:执行什么操作
    • 执行任务方式有两种; 同步/异步
      • 同步:不会到线程池里面去获取子线程!
      • 异步:只要有任务,就会到线程池取子线程!(主队列除外!)
  • 队列:用来存放任务
    • 串行:一个接一个的调度任务
    • 并行:可以同时调度多个任务

GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出

/**
 同步执行方法,这句话不执行完,就不会执行下一个任务,同步执行不会开启线程
 */
   //1.创建队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //2.任务添加到队列中
    //2.1 定义任务 -- block
    void(^task)() = ^{
        NSLog(@"%@",[NSThread currentThread]);
    };
    //2.2 添加任务到队列,并且会执行
    dispatch_sync(q, task);
/**
 异步执行任务  哥么如果任务没有执行完毕,可以不用等待,异步执行下一个任务 
 具备开启线程的能力!  异步通常又是多线程的代名词!!
 */
-(void)gcdDemo2{
    //1.创建队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //2 定义任务 -- block
    void(^task)() = ^{
        NSLog(@"%@",[NSThread currentThread]);
    };
    //3. 添加任务到队列
    dispatch_async(q, task);
}
//线程间通信-切换子线程到主线程更新UI
//指定任务执行方法 -- 异步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //耗时操作
        NSLog(@"%@",[NSThread currentThread]);
        
        //更新UI 主队列,就是专门负责在主线程上调度任务的队列!
        //dispatch_get_main_queue  主队列 住能操作主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI%@",[NSThread currentThread]);
            
        });
        
    });
//MARK:串行队列,同步任务
/**
*   不会开启线程,会顺序执行
*/
-(void)gcdDemo1{
    //1.队列 - 串行
    
    /**
     1."ios" 队列名称:
     2. NULL 队列的属性: DISPATCH_QUEUE_SERIAL 表示串行!
     */
    dispatch_queue_t q = dispatch_queue_create("ios", NULL);
    
    //2.同步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
}
//MARK: 串行队列,异步任务
-(void)gcdDemo2{
    /**
     会开几条线程?会顺序执行吗?
     开一条线程,顺序执行
     */
    //1.队列 - 串行
    dispatch_queue_t q = dispatch_queue_create("tanzhouios", NULL);
    
    //2.异步执行任务
    for (int i = 0; i < 10; i++) {
        NSLog(@"%d------------",i);
        dispatch_async(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}
//MARK : 并发队列,异步执行
-(void)gcdDemo3{
    //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
    
    //2.异步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_async(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}
//MARK : 并发队列,同步执行   和 串行队列,同步执行 效果一样!
-(void)gcdDemo4{
    
    // 会开线程吗?  顺序执行?  come here?
    //  不会          顺序     最后
    
    //1.队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("tanzhouios", DISPATCH_QUEUE_CONCURRENT);
    
    //2.同步执行任务
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    //哥么在主线程!
    NSLog(@"come here");
}

小结:
只有异步才会开启子线程!同步不开启!
开几条线程,取决于队列,串行开一条,并发可以开多条(异步)

个人理解锁这个问题

线程锁:是多条线程同时访问一个资源时需要一个互斥锁;
GDC-死锁:主队列是一个串行队列;在主队列里添加一个同步任务会造成死锁;互相等待的局面;

    //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
    //1.队列 --> 已启动主线程,就可以获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    
    //2.同步任务  ==> 死锁
    //走到这里添加一个同步任务,主队列的任务是不是要等他执行完了在执行;
    dispatch_sync(q, ^{
        NSLog(@"能来吗? ");
        //主队列里的任务没执行完呢你想执行这同步任务,你是不是要等主队列执行完了才能执行;
    });
    NSLog(@"come here");
死锁

注意:
主队列不是全局队列
全局队列:dispatch_get_global_queue
全局队列本质上是并发队列
主队列:dispatch_get_main_queue()
主队列是一个串行队列

主队列是串行的 主队列里的任务默认只有一个(不手动添加的话) 这个任务是同步的 主队列里直接添加一个异步任务 不开开启子线程

主队列只负责主线程

    //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
    
    //1.队列 --> 一启动主线程,就可以获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    
    //2.异步任务
    dispatch_async(q, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    NSLog(@"come here");
    //全局队列
    /*  参数
     1. 涉及到系统适配
     iOS 8   服务质量
     QOS_CLASS_USER_INTERACTIVE    用户交互(希望线程快速被执行,不要用好使的操作)
     QOS_CLASS_USER_INITIATED      用户需要的(同样不要使用耗时操作)
     QOS_CLASS_DEFAULT             默认的(给系统来重置队列的)
     QOS_CLASS_UTILITY             使用工具(用来做耗时操作)
     QOS_CLASS_BACKGROUND          后台
     QOS_CLASS_UNSPECIFIED         没有指定优先级
     iOS 7  调度的优先级
     - DISPATCH_QUEUE_PRIORITY_HIGH 2               高优先级
     - DISPATCH_QUEUE_PRIORITY_DEFAULT 0            默认优先级
     - DISPATCH_QUEUE_PRIORITY_LOW (-2)             低优先级
     - DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
     
     提示:尤其不要选择BACKGROUND 优先级,服务质量,线程执行会慢到令人发指!!!
     
     
     2. 为未来使用的一个保留,现在始终给0.
     
     老项目中,一般还是没有淘汰iOS 7  ,没法使用服务质量
     */
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //以第一个0用来设置优先级
    //第二个0还没有定义
    /*
     全局队列 & 并发队列   区别
     1> 名称,并发队列取名字,适合于企业开发跟踪错误
     2> release,在MRC 并发队列 需要使用的
        dispatch_release(q);//ARC 情况下不需要release !
     
     
     全局队列 & 串行队列
        全局队列: 并发,能够调度多个线程,执行效率高
            - 费电
        串行队列:一个一个执行,执行效率低
            - 省点
     
        判断依据:用户上网方式
            - WIFI : 可以多开线程
            - 流量  : 尽量少开线程
     
     */
    //1.队列
    dispatch_queue_t q = dispatch_queue_create("tanzhou", DISPATCH_QUEUE_CONCURRENT);
    //2.全局队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

 延迟执行
    /**
     从现在开始,进过多少纳秒之后,让 queue队列,调度 block 任务,异步执行!
     参数:
     1.when
     2.queue
     3.block
     */
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.00003 * NSEC_PER_SEC));
    dispatch_after(when, dispatch_queue_create("tanzhou", NULL), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
GCD:执行一次 常用于单利
    //苹果提供的 一次执行机制,不仅能够保证一次执行!而且是线程安全的!!
    static dispatch_once_t onceToken;
    NSLog(@"%ld",onceToken);
    //苹果推荐使用 gcd 一次执行,效率高
    //不要使用互斥锁,效率低!
    dispatch_once(&onceToken, ^{
        //只会执行一次!!
        NSLog(@"执行了%@",[NSThread currentThread]);
    });
GCD调度组
 //1.队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
    //2.调度组
    dispatch_group_t g = dispatch_group_create();
    
    //3.添加任务,让队列调度,任务执行情况,最后通知群组
    dispatch_group_async(g, q, ^{
        NSLog(@"download A%@",[NSThread currentThread]);
    });
    dispatch_group_async(g, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download B%@",[NSThread currentThread]);
    });
    dispatch_group_async(g, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download C%@",[NSThread currentThread]);
    });
    
    //4.所有任务执行完毕后,通知
    //用一个调度组,可以监听全局队列的任务,主队列去执行最后的任务
    //dispatch_group_notify 本身也是异步的!
    dispatch_group_notify(g, dispatch_get_main_queue(), ^{
        //更新UI,通知用户
        NSLog(@"OK %@",[NSThread currentThread]);
    });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,581评论 0 4
  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,085评论 0 23
  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 7,168评论 11 70
  • .一.进程 进程:是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空...
    IIronMan阅读 4,465评论 1 33
  • 在情人节这天,已经不再想着收什么礼物了,因为老公从来没有送过礼物,不同的是,自己不再期盼不再少女心。 今年的情人节...
    韦陀阅读 208评论 0 1