iOS线程、队列解析

基础概念理解

  • 进程:进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程运行在其专用的且受保护的内存中。比如:Windows系统中常用的杀进程,就是彻底关闭一个程序;手机端的爱口袋、我要聘等APP可以看成一个进程。

  • 线程:线程是进程的基本单位,一个进程的所有任务都在线程中执行,进程要想执行任务,必须得有一个或多个线程。程序启动时,默认会开启一条线程,这条线程称之为主线程或者UI线程。

  • 队列:队列是任务的集合,强调的是一种静态表示。或者可以说,队列是按先进先出(FIFO)管理任务对象的数据结构;通常我们将队列分为4种:串行队列、并发队列、主队列、全局队列。

  • 同步(sync):任务一个接着一个,前一个没有执行完,后面不能执行,没有创建新线程的能力。

  • 异步(async):开启多个新线程,任务同一时间可以一起执行。异步有开启新线程的能力,但是不一定会开启新线程(在主线程上就不会开新线程)。严格来说,异步才有多线程的概念,是多线程的代名词。

多线程的意义

  • 优点:
    1、能适当提高程序的执行效率
    2、能适当提高资源利用率(CPU、内存)
    3、线程上的任务执行完成后,线程会自动销毁
  • 缺点:
    1、开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB)
    2、如果开启大量线程,会占用大量的内存空间,降低程序的性能
    3、线程越多,CPU在线程调度上的开销就越大
    4、程序设计更加复杂,比如线程间的通信、多线程的数据共享(比如2个人买同一张票)

iOS四种队列

  • 串行队列:
dispatch_queue_create("queue.name", DISPATCH_QUEUE_SERIAL);

所有任务按顺序依次执行,结束顺序固定,符合先进先出的基本原则,队列后面的任务必须等待前面的任务执行完毕后才出队列。但是,不要认为串行队列中的所有任务都在同一个线程中执行,串行队列中的异步任务,可能会开启新线程去执行。

  • 并发队列
dispatch_queue_create("queue.name", DISPATCH_QUEUE_CONCURRENT);

所有任务可以同时执行,结束顺序不固定,只要有可用线程,则队列头部任务将持续出队列。

  • 主队列:
dispatch_get_main_queue()

本质是一个特殊的串行队列,主队列的任务都在主线程来执行,专门负责调度主线程度的任务,无法开辟新的线程。所以,在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。如果在主线程上已经有任务正在执行,主队列会等到主线程空闲后再调度任务。

  • 全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

本质是一个特殊的并发队列。在后面加入了“服务质量”和“调度优先级” 两个参数

线程与队列的组合分析

  • 串行队列+异步:顺序执行,先进先出,可能会开启新线程
//串行队列
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//异步任务
for (int i=0; i<5; i++) {

    dispatch_async(serial, ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });
}
NSLog(@"=============== 所有任务执行完毕 =====");

执行结果:
2022-05-17 15:27:35.976433+0800 myDemo[278:9179] =============== 所有任务执行完毕 =====
2022-05-17 15:27:35.976538+0800 myDemo[278:9420] 这是第 0 个任务;线程 <NSThread: 0x283628500>{number = 4, name = (null)}
2022-05-17 15:27:35.976655+0800 myDemo[278:9420] 这是第 1 个任务;线程 <NSThread: 0x283628500>{number = 4, name = (null)}
2022-05-17 15:27:37.978072+0800 myDemo[278:9420] 这是第 2 个任务;线程 <NSThread: 0x283628500>{number = 4, name = (null)}
2022-05-17 15:27:37.978150+0800 myDemo[278:9420] 这是第 3 个任务;线程 <NSThread: 0x283628500>{number = 4, name = (null)}
2022-05-17 15:27:39.987439+0800 myDemo[278:9420] 这是第 4 个任务;线程 <NSThread: 0x283628500>{number = 4, name = (null)}

  • 串行队列+同步:顺序执行,先进先出,不会开启新线程
//串行队列
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//同步任务
for (int i=0; i<5; i++) {

    dispatch_sync(serial, ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });

执行结果:

2022-05-17 15:14:04.059889+0800 myDemo[252:4951] 这是第 0 个任务;线程 <NSThread: 0x282423f80>{number = 1, name = main}
2022-05-17 15:14:04.059996+0800 myDemo[252:4951] 这是第 1 个任务;线程 <NSThread: 0x282423f80>{number = 1, name = main}
2022-05-17 15:14:06.060559+0800 myDemo[252:4951] 这是第 2 个任务;线程 <NSThread: 0x282423f80>{number = 1, name = main}
2022-05-17 15:14:06.060716+0800 myDemo[252:4951] 这是第 3 个任务;线程 <NSThread: 0x282423f80>{number = 1, name = main}
2022-05-17 15:14:08.061826+0800 myDemo[252:4951] 这是第 4 个任务;线程 <NSThread: 0x282423f80>{number = 1, name = main}
  • 并发队列+异步:同时执行,每个任务的结束顺序不确定,会开启新线程
//并发队列
dispatch_queue_t comp = dispatch_queue_create("comp", DISPATCH_QUEUE_CONCURRENT);
//异步任务
for (int i=0; i<5; i++) {

    dispatch_async(comp, ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });
}
NSLog(@"=============== 所有任务执行完毕 =====");

执行结果:
2022-05-17 15:39:06.309859+0800 myDemo[294:11951] =============== 所有任务执行完毕 =====
2022-05-17 15:39:06.309962+0800 myDemo[294:12131] 这是第 0 个任务;线程 <NSThread: 0x281719380>{number = 5, name = (null)}
2022-05-17 15:39:06.310064+0800 myDemo[294:12131] 这是第 1 个任务;线程 <NSThread: 0x281719380>{number = 5, name = (null)}
2022-05-17 15:39:06.310165+0800 myDemo[294:12134] 这是第 2 个任务;线程 <NSThread: 0x2817d2300>{number = 4, name = (null)}
2022-05-17 15:39:06.310236+0800 myDemo[294:12134] 这是第 3 个任务;线程 <NSThread: 0x2817d2300>{number = 4, name = (null)}
2022-05-17 15:39:06.311765+0800 myDemo[294:12130] 这是第 4 个任务;线程 <NSThread: 0x281719d40>{number = 6, name = (null)}

  • 并发队列+同步:顺序执行,先进先出,不会开启新线程
//并发队列
dispatch_queue_t comp = dispatch_queue_create("comp", DISPATCH_QUEUE_CONCURRENT);
//同步任务
for (int i=0; i<5; i++) {

    dispatch_sync(comp, ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });
}
NSLog(@"=============== 所有任务执行完毕 =====");

执行结果:
2022-05-17 15:37:08.804872+0800 myDemo[287:11040] 这是第 0 个任务;线程 <NSThread: 0x282b3fac0>{number = 1, name = main}
2022-05-17 15:37:08.804962+0800 myDemo[287:11040] 这是第 1 个任务;线程 <NSThread: 0x282b3fac0>{number = 1, name = main}
2022-05-17 15:37:10.805818+0800 myDemo[287:11040] 这是第 2 个任务;线程 <NSThread: 0x282b3fac0>{number = 1, name = main}
2022-05-17 15:37:10.806122+0800 myDemo[287:11040] 这是第 3 个任务;线程 <NSThread: 0x282b3fac0>{number = 1, name = main}
2022-05-17 15:37:12.807415+0800 myDemo[287:11040] 这是第 4 个任务;线程 <NSThread: 0x282b3fac0>{number = 1, name = main}
2022-05-17 15:37:12.807771+0800 myDemo[287:11040] =============== 所有任务执行完毕 =====
  • 主队列+异步:不会立即执行,而是等待主队列中的所有其他除我们添加到主队列的任务都执行完毕之后,才会执行我们添加的任务
  • 主队列+同步:会相互等待导致死锁
//同步任务+主线程 == 相互等待,死锁
for (int i=0; i<5; i++) {

        dispatch_sync(dispatch_get_main_queue(), ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });
}

iOS的四种代码实现方式

pthread

C语言通用的多线程API,跨平台,程序员手动管理线程生命周期,使用难度大

NSThread

一个 NSThread 对象就代表一条线程,使用较少

//先创建再启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"];
[thread start];

//直接创建并启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"jack"];

//直接创建并启动线程
[self performSelectorInBackground:@selector(run:) withObject:@"jack"];

//使线程进入阻塞状态
[NSThread sleepForTimeInterval:2.0];

GCD

GCD 是苹果公司为多核的并行运算提出的解决方案, GCD会自动利用更多的 CPU 内核(比如双核、四核)来开启线程执行任务,GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),不需要我们程序员手动管理内存。

补充一个栅栏的示例代码:
//并发队列
dispatch_queue_t comp = dispatch_queue_create("comp", DISPATCH_QUEUE_CONCURRENT);
//异步任务
for (int i=0; i<5; i++) {

    dispatch_async(comp, ^{

        NSLog(@"这是第 %d 个任务;线程 %@",i,[NSThread currentThread]);
        if (i==1 || i==3) {
            [NSThread sleepForTimeInterval:2];
        }
    });
}
    
dispatch_barrier_async(comp, ^{

    NSLog(@"这是第 dispatch_barrier_async 个任务;线程 %@",[NSThread currentThread]);
});
NSLog(@"=============== 所有任务执行完毕 =====");

执行结果:
2022-05-17 16:17:57.471306+0800 myDemo[321:16764] =============== 所有任务执行完毕 =====
2022-05-17 16:17:57.471536+0800 myDemo[321:16980] 这是第 1 个任务;线程 <NSThread: 0x2825a7600>{number = 6, name = (null)}
2022-05-17 16:17:57.471645+0800 myDemo[321:16985] 这是第 0 个任务;线程 <NSThread: 0x2825b83c0>{number = 5, name = (null)}
2022-05-17 16:17:57.471751+0800 myDemo[321:16985] 这是第 2 个任务;线程 <NSThread: 0x2825b83c0>{number = 5, name = (null)}
2022-05-17 16:17:57.471894+0800 myDemo[321:16985] 这是第 3 个任务;线程 <NSThread: 0x2825b83c0>{number = 5, name = (null)}
2022-05-17 16:17:57.472199+0800 myDemo[321:16979] 这是第 4 个任务;线程 <NSThread: 0x282546580>{number = 3, name = (null)}
2022-05-17 16:17:59.495622+0800 myDemo[321:16985] 这是第 dispatch_barrier_async 个任务;线程 <NSThread: 0x2825b83c0>{number = 5, name = (null)}

NSOperation、NSOperationQueue

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。

//创建操作方法,启动
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationLoad:) object:@{@"obj":@"loadContent"}];
[invocationOperation start];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    [self operationLoad:@{@"operation":@"11111111111"}];
}];
[operation start];
NSLog(@"invocationOperation/operation 开启start命令");
//创建队列
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *operationTwo = [NSBlockOperation blockOperationWithBlock:^{
    [self operationLoad:@{@"operation":@"2222222222"}];
}];
[opQueue addOperation:operationTwo];
//队列直接添加block方法
[opQueue addOperationWithBlock:^{
    [self operationLoad:@{@"addOperationWithBlock":@"333333333"}];
}];
NSBlockOperation *operationFive = [NSBlockOperation blockOperationWithBlock:^{
    [self operationLoad:@{@"operation":@"5555555555"}];
}];
//队列添加任务组
[opQueue addOperations:@[operationFive] waitUntilFinished:NO];
//添加栅栏功能
if (@available(iOS 13.0, *)) {
    [opQueue addBarrierBlock:^{
        [self operationLoad:@{@"addBarrierBlock":@"888888888"}];
    }];
} else {
    // Fallback on earlier versions
}
    
NSLog(@"=============== 所有任务执行完毕 =====");
    
执行结果:
2022-05-17 16:54:34.193616+0800 myDemo[356:25036] 传递参数== {
    obj = loadContent;
} ,线程== <NSThread: 0x28147a9c0>{number = 1, name = main}
2022-05-17 16:54:36.196363+0800 myDemo[356:25036] 传递参数== {
    operation = 11111111111;
} ,线程== <NSThread: 0x28147a9c0>{number = 1, name = main}
2022-05-17 16:54:36.196762+0800 myDemo[356:25036] invocationOperation/operation 开启start命令
2022-05-17 16:54:36.197310+0800 myDemo[356:25036] =============== 所有任务执行完毕 =====
2022-05-17 16:54:38.321310+0800 myDemo[356:25232] 传递参数== {
    operation = 5555555555;
} ,线程== <NSThread: 0x281424e40>{number = 4, name = (null)}
2022-05-17 16:54:38.322240+0800 myDemo[356:25237] 传递参数== {
    addOperationWithBlock = 333333333;
} ,线程== <NSThread: 0x2814259c0>{number = 3, name = (null)}
2022-05-17 16:54:38.322340+0800 myDemo[356:25231] 传递参数== {
    operation = 2222222222;
} ,线程== <NSThread: 0x281824740>{number = 14, name = (null)}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容