iOS 多线程NSThread

一、什么是多线程

  • 多线程是指从软件或者硬件上实现多个线程并发执行的技术。
  • 由于一条线程同一时间只能处理一个任务,所以线程里的任务必须按顺序执行。
  • 如果遇到耗时操作(网络通信、耗时计算、音乐播放等),等上一个操作完成再执行下一个任务,在此段时间内,用户得不到任何响应,这个体验无疑是极糟的。
  • 因此,在iOS编程中,通常将耗时操作单独放在一个线程中,而把用户交互的操作放在主线程中

二、进程与线程

  • 进程:
进程是指系统中正在运行的应用程序。这个'运行中的程序'就是一个进程。
每个进程都拥有着自己的地址空间。

进程有3个主要特征:

独立性:  
进程是一个能够独立运行的基本单位,它既拥有自己独立的资源,又拥有着自己私有的地址空间。
在没有经过进程本身允许的情况下,一个用户的进程是不可以直接访问其他进程的地址空间的。 

动态性:    
进程的实质是程序在系统中的一次执行过程。
程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。
在进程中加入了时间的概念,它就具有了自己的生命周期和各自不同的状态,进程是动态消亡的。
  
并发性:  
多个进程可以在单个处理器中同时进行,而不会相互影响。

  • 线程:
多线程扩展了多进程的概念,使得同一进程可以同时并发处理多个任务。
一个程序的运行至少需要一个线程,一个进程想要执行任务,也必须要有至少一个线程,而这个线程就被称作主线程。 
通常来说,只有一个主线程。
当进程被初始化后,主线程就被创建了,主线程是其他线程最终的父线程,所有界面的显示操作必须在主线程进行。

三、多线程的优势

  1. 进程间不能共享内存,但线程之间的共享内存是很容易的。

  2. 当硬件处理器的数量有所增加时,程序运行的速度更快,无需做其他调整。

  3. 充分发挥了多核处理器的优势,将不同的任务分配给不同的处理器,真正进入“并行运算”的状态。

  4. 将耗时、并发需求高的任务分配到其他线程执行,而主线程则负责统一更新界面,这样使得应用程序更加流畅,用户体验更好。

四、多线程的劣势

  1. 开启线程需要占用一定的内存空间[默认情况下,主线程最大占用1M的栈区空间、子线程最大占用512K的栈区空间],如果要开启大量的线程,势必会占用大量的内存空间,从而降低程序的性能。
  2. 开启的线程越多,CPU在调度线程上的开销就越大,一般最好不要同时开启超过5个线程
  3. 程序的设计会变得更加复杂,如线程之间的通信、多线程间的数据共享等。

五、线程的串行和并行

  • 串行:
如果在一个进程中只有一个线程,而这个进程要执行多个任务,
那么这个线程只能一个一个的按照顺序执行这些任务,也就是说,
在同一时间内,一个线程只能执行一个任务,这样的线程执行方式称为线程的串行。
  • 并行:
如果一个进程中包含的线程不止一条,
每条线程之间可以并行执行不同的任务,这叫做线程的并行,也叫多线程。
  • 补充:
假如一个进程有3个线程,每个线程执行1个任务,3个下载任务没有先后顺序。可以同时执行。 
同一时间,CPU只能处理一个线程,也就是只有一个线程在工作。
由于CPU快速的在多个线程之间调度,人眼无法察觉到,就造成了多线程并发执行的假象。

六、线程的状态

当线程被创建并启动之后,既不是一启动就进入执行状态,也不是一直处于执行状态,即便程序启动运行之后,它也不可能一直占用CPU独自运行。

由于CPU需要在多个线程之间进行切换,造成了线程的状态会在多次运行、就绪之间进行切换。

线程的状态主要有5个:

1. 新建New        
当程序新建一个线程之后,该线程就处于新建状态。
和其他对象一样,只是由系统分配了内存,并初始化了内部成员变量的值。
此时的线程没有任何动态特征

2. 就绪Runable
当线程被start之后,该线程就处于就绪状态。
系统会为其创建 方法调用的栈和 程序计数器。

3. 运行Running
当CPU调度当前线程的时候,将其他线程挂起,当前线程变为运行状态。
当CPU调度其他线程时,当前线程回到就绪状态。
测试线程是否在运行,调用isExecuting方法,若返回YES,则处于运行状态。

4. 终止Exit
* 线程执行方法执行完成,线程正常结束
* 线程执行的过程出现异常,线程崩溃结束
* 直接调用NSThread类的exit方法,终止当前正在运行的线程
* 测试线程是否结束,调用isFinished方法判断,若返回YES,则已终止。

5. 阻塞Blocked    
如果当前正在执行的线程需要暂停一段时间,并进入阻塞状态,通过NSThread类的两个方法:

//让当前执行的线程暂停到date参数代表的时间,并且进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;

//让正在执行的线程暂停ti秒,并进入阻塞状态
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

七、线程间的安全隐患

进程中的一块资源可能会被多个线程共享,也就是多个线程可能访问同一块资源,这里的资源包括对象变量文件等。
当多个线程同时访问同一块资源时,会造成资源抢夺,很容易引发数据错乱数据安全的问题。
为了解决这个问题,实现数据的安全访问,可以使用线程间的加锁

  • 数据混乱示例:
/** 售票处理 */
- (void)saleTickets{
    while (true) {
        //模拟延时
        [NSThread sleepForTimeInterval:1.0];
        
        //判断是否还有票
        if (self.leftTickets > 0) {
            self.leftTickets--;
            
            NSLog(@"%@卖了一张票,还剩下%lu张票",[NSThread currentThread].name,self.leftTickets);
        } else{
            NSLog(@"票已售完");
            return;
        }
    }
}


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //设定总票数
    self.leftTickets = 100;
    //创建3个线程,启动后执行saleTickets方法卖票
    NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"1号窗口";
    [t1 start];
    
    NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"2号窗口";
    [t2 start];
    
    NSThread *t3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
    t3.name = @"3号窗口";
    [t3 start];
}
未加锁.png

可以看到,由于3个线程的并发操作,同一时间抢夺一个资源leftTickets,造成了剩余票数统计的混乱。

  • 加锁示例:
@synchronized (obj)
{
    //插入被修饰的代码块
}
  • 使用同步锁的时候,要尽量让同步代码块包围的代码范围最小,而且要锁定共享资源的全部读写部分的代码。

obj就是加锁对象,添加了锁对象后,锁对象实现了对多线程的监管,保证同一时刻只有一个线程执行,当同步代码块执行完成后,锁定对象就会释放同步监视器的锁定。

/** 售票处理 */
- (void)saleTickets{
    while (true) {
        //模拟延时
        [NSThread sleepForTimeInterval:1.0];
        
        //判断是否还有票
        @synchronized(self){
            if (self.leftTickets > 0) {
                self.leftTickets--;
                
                NSLog(@"%@卖了一张票,还剩下%lu张票",[NSThread currentThread].name,self.leftTickets);
            } else{
                NSLog(@"票已售完");
                return;
            }
        }
    }
}
加锁.png

线程添加同步锁后,实现了线程的同步运行,也就是说,使多线程按顺序执行任务。需要注意的是,同步锁会消耗大量的CPU资源。

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

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,126评论 0 23
  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,739评论 0 17
  • 第三章 Java内存模型 3.1 Java内存模型的基础 通信在共享内存的模型里,通过写-读内存中的公共状态进行隐...
    泽毛阅读 4,352评论 2 22
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,649评论 2 17
  • 亲爱的儿子 你就月份就要开学了。但是现在每天妈妈布置给你的作业你依旧很不情愿的做。 妈妈希望随着你的长大。您能够学...
    我是小小彦阅读 157评论 0 1