发布于 

iOS 多线程: 初步认识

几乎每个编程语言或者平台都会遇到多线程的问题, 说明多线程是一个非常重要且开发者必须了解和掌握的.

多线程也是面试官比较喜欢问的问题, 例如:

  • 进程和线程的区别, Android 是否支持多进程?
  • 线程池如何实现的?
  • 锁机制?
  • 多线程之间如何通信?

谈及 iOS 中的多线程,一般说的是 pthread,NSthread,GCD,NSOperation 这四种, 用的最多也最方便的就是 GCD 了. 关于这四者, 后续会为大家一一分享.

phtread 是跨平台的, C/C++ 中都有它的声影, GCD 和 NSOperation 都是常用的,NSOperation 是基于 GCD 的. GCD 的核心概念是将一个任务添加到队列,指定任务执行的方法,然后执行, NSOperation 则是直接将一个操作添加到队列中.

该系列文章来跟大家分享关于 iOS 中的多线程.

  • iOS 多线程: 初步认识(本篇)

进程和线程

进程和线程的定义, 大家可以自行到维基百科上面去查.

这里只说二者的区别.

  • 一个程序至少有一个进程, 一个进程至少有一个线程如主线程.

  • 多线程程序的并发性高.

  • 进程在执行过程中拥有独立的内存单元,而多线程是共享内存的,从而极大地提高了程序的运行效率.

  • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口. 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制.

  • 操作系统并没有将多个线程看做多个独立的应用, 多线程的意义在于一个应用程序中,有多个执行部分可以同时执行, 从而实现了进程的调度和管理以及资源分配.

队列

iOS 中, 队列主要分为:

  • 全局队列
  • 主队列.
  • 串行队列.
  • 并发队列.
  • Concurrent:
    tasks are dequeued in FIFO order, but run concurrently and can finish in any order.
  • Serial: .
    tasks execute one at a time in FIFO order

并发: 任务以 FIFO 从序列中移除,然后并发运行,可以按照任何顺序完成.

串行: 任务以FIFO从序列中一个一个执行. 一次只调度一个任务.

在 iOS 中, 并发不一定会开启多个线程, 串行也不一定只开启一个线程. 因为这里会牵扯到是异步还是同步执行.

主队列, 即 mainQueue.

Returns the default queue that is bound to the main thread.

会关联主线程.

全局队列, 即 globalQueue.

The well-known global concurrent queues may not be modified.

全局队列中执行不一定会开启新线程.

同步和异步

在 iOS 的 GCD 中, 还有同步和异步执行的区别.

同步

同步执行代码块, 诸如 dispatch_async 中执行的.

异步

异步执行代码块, 诸如 dispatch_sync 中执行的.

创建队列的方法

创建主队列

1
dispatch_queue_t dispatch_get_main_queue(void);

创建全局队列

1
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

参数 identifier 用来表示优先级. 对应的优先级为:

  • DISPATCH_QUEUE_PRIORITY_HIG
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND

如果传入 0 标示 DISPATCH_QUEUE_PRIORITY_DEFAULT.

参数 flags 是一个保留参数, API 文档要求传入 0, 非0值可能会导致返回结果为 NULL.

自定义队列

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

参数 label 是一个字符串.

参数 attr 用来标示是串行还是并行队列. 可以从 DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT 二者中取值.

如果该参数传入 NULL, 默认是 DISPATCH_QUEUE_SERIAL 串行队列.

串行队列中的线程

串行队列到底是开了一个线程, 还是开了多个线程, 我们一探究竟.

先看例子1

1
2
3
4
5
6
7
8
dispatch_queue_t serialQueue = dispatch_queue_create("com.veryitman", DISPATCH_QUEUE_SERIAL);

for (int i=0; i<10; i++) {

dispatch_async(serialQueue, ^{
NSLog(@"dispatch_async. DISPATCH_QUEUE_SERIAL CurrentThread: %@", [NSThread currentThread]);
});
}

1

可以看出, 只开启一个新线程.

再看例子2

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t serialQueue = dispatch_queue_create("com.veryitman", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{
NSLog(@"dispatch_async. DISPATCH_QUEUE_SERIAL CurrentThread: %@", [NSThread currentThread]);
sleep(2);
});

dispatch_async(serialQueue, ^{
NSLog(@"dispatch_sync. DISPATCH_QUEUE_SERIAL CurrentThread: %@", [NSThread currentThread]);
});

1

可以看出, 也只开启一个新线程.

改造一下例子2, 将其中的一个异步改为同步

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t serialQueue = dispatch_queue_create("com.veryitman", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{
NSLog(@"dispatch_async. DISPATCH_QUEUE_SERIAL CurrentThread: %@", [NSThread currentThread]);
sleep(2);
});

dispatch_sync(serialQueue, ^{
NSLog(@"dispatch_sync. DISPATCH_QUEUE_SERIAL CurrentThread: %@", [NSThread currentThread]);
});

1

可以看出, 同步的执行在主线程, 二者并不是在一个线程中执行.

所以, 串行队列中执行的代码, 不一定都在子线程中, 如果是异步都是同一个线程中执行.如果是同步的话, 会在主线程中执行.

同理, 并行队列中的异步执行会开启多个线程来执行.

执行方式和队列

这里的执行方式指的是同步或者异步执行.

先看异步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// 异步全局队列和主队列
{
dispatch_async(dispatch_get_main_queue(), ^{
// 1
NSLog(@"dispatch_async. mainQueue. isMainThread: %i", [NSThread isMainThread]);
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 0
NSLog(@"dispatch_async. globalQueue. isMainThread: %i", [NSThread isMainThread]);
});
}

/// 异步串行和并行队列
{
dispatch_async(dispatch_queue_create("", DISPATCH_QUEUE_SERIAL), ^{
// 0
NSLog(@"dispatch_async. DISPATCH_QUEUE_SERIAL. isMainThread: %i", [NSThread isMainThread]);
});

dispatch_async(dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT), ^{
// 0
NSLog(@"dispatch_async. DISPATCH_QUEUE_CONCURRENT. isMainThread: %i", [NSThread isMainThread]);
});
}

1

再看同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// 同步全局队列和主队列
{

#if 0
// 会死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_sync. mainQueue. isMainThread: %i", [NSThread isMainThread]);
});
#endif

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
// 1
NSLog(@"dispatch_sync. globalQueue. isMainThread: %i", [NSThread isMainThread]);
});
}

/// 同步串行和并行队列
{
dispatch_sync(dispatch_queue_create("", DISPATCH_QUEUE_SERIAL), ^{
// 1
NSLog(@"dispatch_sync. DISPATCH_QUEUE_SERIAL. isMainThread: %i", [NSThread isMainThread]);
});

dispatch_sync(dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT), ^{
// 1
NSLog(@"dispatch_sync. DISPATCH_QUEUE_CONCURRENT. isMainThread: %i", [NSThread isMainThread]);
});
}

1

得出结论

  • 任何队列的同步执行, 都没有开启新线程, 在主线程中执行.
  • 主队列的同步执行会造成死锁.
  • 主队列的异步执行, 没有开启新线程. 在主线程中执行.
  • 串行和并行以及全局队列的异步执行, 都会开启新线程.

参考


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

veryitman