iOS——多线程理解

首先先说明此文是学习了李峰峰大牛的博客后所写,有兴趣的可以百度搜索一下李峰峰的博客。

一、线程和进程

1、线程

线程,是程序执行流的最小单元,线程是程序中一个单一的顺序控制流程。是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位,指运行中的程序的调度单位。

简单来说,1个进程要想执行任务,必须得有线程。

线程中任务的执行是串行的,要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务,由此可以理解线程是进程中的1条执行路径。

一个进程中至少包含一条线程,即主线程,创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行。

2、进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,每一个进程都有自己独立的虚拟内存空间。

简单来说,进程是指在系统中正在运行的一个应用程序,每一个程序都是一个进程,并且进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

二、多线程

1.多线程简介

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

原理:

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

优点:

  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU、内存利用率)

缺点:

  • 开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU在调度线程上的开销就越大

主线程栈区的1M,非常非常宝贵。一个进程,至少有一个线程(主线程),不能杀掉一个线程!但是可以暂停、休眠。

2.iOS中多线程的实现方式

  • NSThread:

(1)使用NSThread对象建立一个线程非常方便

(2)但是!要使用NSThread管理多个线程非常困难,不推荐使用

(3)技巧!使用[NSThread currentThread]获得任务所在线程,适用于这三种技术

(4)使线程休眠3秒:[NSThread sleepForTimeInterval:0.3f];

  • GCD —— Grand Central Dispatch:

(1)是基于C语言的底层API

(2)用Block定义任务,使用起来非常灵活便捷

(3)提供了更多的控制能力以及操作队列中所不能使用的底层函数

  • NSOperation/NSOperationQueue:

(1)是使用GCD实现的一套Objective-C的API

(2)是面向对象的线程技术

(3)提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系

3.多线程的安全问题

多个线程访问同一块资源进行读写,如果不加控制随意访问容易产生数据错乱从而引发数据安全问题。为了解决这一问题,就有了加锁的概念。加锁的原理就是当有一个线程正在访问资源进行写的时候,不允许其他线程再访问该资源,只有当该线程访问结束后,其他线程才能按顺序进行访问。对于读取数据,有些程序设计是允许多线程同时读的,有些不允许。UIKit中几乎所有控件都不是线程安全的,因此需要在主线程上更新UI。

解决多线程安全问题:

(1)互斥锁

// 注意:锁定1份代码只用1把锁,用多把锁是无效的
@synchronized(锁对象) { // 需要锁定的代码  }

使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码。因为互斥锁的代价非常昂贵,所以锁定的代码范围应该尽可能小,只要锁住资源读写部分的代码即可。使用互斥锁也会影响并发的目的。

(2)使用NSLock对象

_lock = [[NSLock alloc] init];
 - (void)synchronizedMethod {
    [_lock lock];
    //safe
    [_lock unlock];
 }

(3)atomic加锁,加锁过程系统已自动完成

OC在定义属性时有nonatomic和atomic两种选择。

atomic:原子属性,为setter方法加锁(默认就是atomic)。

nonatomic:非原子属性,不会为setter方法加锁。

atomic加锁原理:

 @property (assign, atomic) int age;
 
 - (void)setAge:(int)age
 { 
 
     @synchronized(self) { 
        _age = age;
     }
 }

atomic:线程安全,需要消耗大量的资源

nonatomic:非线程安全,适合内存小的移动设备

iOS开发的建议:

(1)所有属性都声明为nonatomic

(2)尽量避免多线程抢夺同一块资源

(3)尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

4.多线程间的通信

//在主线程上执行操作,例如给UIImageVIew设置图片
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
//在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait

三、GCD的介绍

GCD(Grand Central Dispatch) 伟大的中央调度系统,是苹果为多核并行运算提出的C语言并发技术框架。

GCD会自动利用更多的CPU内核;
会自动管理线程的生命周期(创建线程,调度任务,销毁线程等);
程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码。

  • 多线程中的一些概念
  • dispatch :派遣/调度
  • queue:队列——用来存放任务的先进先出(FIFO)的容器
  • sync:同步——只是在当前线程中执行任务,不具备开启新线程的能力
  • async:异步——可以在新的线程中执行任务,具备开启新线程的能力
  • concurrent:并发——指一个处理器,宏观上同时处理多个任务。
  • 串行:一个任务执行完毕后,再执行下一个任务
  • 并行:指多个处理器或者是多核的处理器同时处理多个不同的任务。
并行和并发的详细介绍:
  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
sync和async的详细介绍:
  • dispatch_sync()——同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:10];
        NSLog(@"3");
    });
    NSLog(@"4");

//控制台打印
11:36:25.313 GCDSeTest[544:303] 1
11:36:25.313 GCDSeTest[544:303] 2
11:36:30.313 GCDSeTest[544:303] 3//模拟长时间操作
11:36:30.314 GCDSeTest[544:303] 4

  • dispatch_async ,异步添加进任务队列,它不会做任何等待
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

//控制台打印
11:42:43.820 GCDSeTest[568:303] 1
11:42:43.820 GCDSeTest[568:303] 4
11:42:43.820 GCDSeTest[568:1003] 2
11:42:48.821 GCDSeTest[568:1003] 3//模拟长时间操作时间

  • 线程死锁
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"=================4");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"=================5");
    });
    NSLog(@"=================6");
}

dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。

dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,及阻塞当前线程,等待 block同步执行完成。

分析上面代码:

viewDidLoad 在主线程中, 及在
dispatch_get_main_queue() 中,执行到sync 时 向
dispatch_get_main_queue()插入 同步 threed1.

sync 会等到 后面block 执行完成才返回, sync 又再 dispatch_get_main_queue() 队列中,
它是串行队列,sync 是后加入的,前一个是主线程,
所以 sync 想执行 block 必须等待主线程执行完成,主线程等待 sync 返回,去执行后续内容。

造成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。

  • 这种情况又不会造成线程死锁。
dispatch_async(dispatch_get_global_queue(0, 0), ^{

    NSLog(@"=================1");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"=================2");
    });
    NSLog(@"=================3");
});

程序会完成执行,为什么不会出现死锁。

首先: async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回,

1,async 立即返回, viewDidLoad 执行完毕,及主线程执行完毕。
2,同时,全局并发队列立即执行异步 block , 打印 1, 当执行到 sync 它会等待 block 执行完成才返回, 及等待dispatch_get_main_queue() 队列中的 mianThread 执行完成, 然后才开始调用block 。

因为1 和 2 几乎同时执行,因为2 在全局并发队列上, 2 中执行到sync 时 1 可能已经执行完成或 等了一会,mainThread 很快退出, 2 等已执行后续内容。

dispatch中的其他用法

(1)延时执行
//参数1:从现在开始经过多少纳秒,参数2:调度任务的队列,参数3:异步执行的任务
dispatch_after(when, queue, block)
例如:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});

(2)一次性执行
应用场景:保证某段代码在程序运行过程中只被执行一次,在单例设计模式中被广泛使用。
// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

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

推荐阅读更多精彩内容