本笔记整理自 iOS基础之搞定多线程,详情请参考原文。
一、pThread(很少使用)
基于 C 语言的
- (void)runThread {
pthread_t pthread;
pthread_create(&pthread, NULL, myWork, NULL);
}
void *myWork(void *data) {
for (int i = 0; i < 10; i++) {
NSLog(@"%d", i);
sleep(1);
}
return NULL;
}
二、NSThread
(一)NSThread 的创建方式
1、直接创建 NSThread 对象
- (void)doMyWork {
NSLog(@"在子线程中运行 - [%@]", [NSThread currentThread].name);
for (int i = 0; i < 10; i++) {
NSLog(@"子线程 - [%@], 输出 %d",
[NSThread currentThread].name,
i);
sleep(1);
}
}
- (void)runThread {
NSLog(@"在主线程中运行");
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(doMyWork)
object:nil];
[thread start];
NSLog(@"已经启动线程");
}
2、通过 NSThread 类方法创建并执行线程
- (void)runThread2 {
[NSThread detachNewThreadSelector:@selector(doMyWork)
toTarget:self
withObject:nil];
}
(二)NSThread 的设置
- (void)runThread {
NSLog(@"在主线程中运行");
NSThread *thread1 = [[NSThread alloc] initWithTarget:self
selector:@selector(doMyWork)
object:nil];
thread1.name = @"Thread 1";
thread1.threadPriority = 0.2;
NSThread *thread2 = [[NSThread alloc] initWithTarget:self
selector:@selector(doMyWork)
object:nil];
thread2.name = @"Thread 2";
thread2.threadPriority = 0.5;
[thread1 start];
[thread2 start];
NSLog(@"已经启动线程");
}
(三)NSThread 锁
1、并发访问全局资源带来的问题
@implementation ThreadTester {
int nCount;
}
// 初始化 nCount
- (instancetype)init {
if (self = [super init]) {
nCount = 100;
}
return self;
}
// 执行 --nCount,分别打印 执行 -1 操作 之前和之后的值
// 输出 -1 之后的值再 +1 是否等于原值
// 多线程的场景发现并不成立
- (void)doMyWork {
while (nCount >= 0) {
int val1 = nCount;
sleep(1);
int val2 = --nCount;
NSLog(@"在子线程中运行 - [%@], oldVal = %d, newVal = %d, sum = %d",
[NSThread currentThread].name, val1, val2, val2 + 1 == val1);
}
}
// 启动两个线程执行 -1 操作
- (void)runThread {
NSLog(@"在主线程中运行");
NSThread *thread1 = [[NSThread alloc] initWithTarget:self
selector:@selector(doMyWork)
object:nil];
thread1.name = @"Thread 1";
NSThread *thread2 = [[NSThread alloc] initWithTarget:self
selector:@selector(doMyWork)
object:nil];
thread2.name = @"Thread 2";
[thread1 start];
[thread2 start];
NSLog(@"已经启动线程");
}
@end
2、解决方案
(1)@synchronized
- (void)doMyWork {
while (nCount >= 0) {
@synchronized (self) { // 使用同步锁
int val1 = nCount;
sleep(1);
int val2 = --nCount;
NSLog(@"在子线程中运行 - [%@], oldVal = %d, newVal = %d, sum = %d",
[NSThread currentThread].name, val1, val2, val2 + 1 == val1);
}
}
}
(2)NSCondition
- (void)doMyWork {
while (nCount >= 0) {
[condition lock];
int val1 = nCount;
sleep(1);
int val2 = --nCount;
NSLog(@"在子线程中运行 - [%@], oldVal = %d, newVal = %d, sum = %d",
[NSThread currentThread].name, val1, val2, val2 + 1 == val1);
[condition unlock];
}
}
三、GCD
(一)最常见的用法
场景:异步执行耗时任务,完成后在主线程执行显示结果。
- (void)runGCD {
NSLog(@"start in main...");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start in global...");
sleep(3);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"start in main queue...");
sleep(2);
NSLog(@"finish in main queue");
});
NSLog(@"finish in global...");
});
[NSThread sleepForTimeInterval:2];
NSLog(@"finish in main");
}
执行结果:
2019-10-16 06:31:56.317 TestThread[51803:722348] start in main...
2019-10-16 06:31:56.318 TestThread[51803:722454] start in global...
2019-10-16 06:31:58.319 TestThread[51803:722348] finish in main
2019-10-16 06:31:59.321 TestThread[51803:722454] finish in global...
2019-10-16 06:31:59.322 TestThread[51803:722348] start in main queue...
2019-10-16 06:32:01.322 TestThread[51803:722348] finish in main queue
(二)全局队列 dispatch_get_global_queue
dispatch_get_global_queue
的声明:
dispatch_get_global_queue(long identifier, unsigned long flags);
identifier
:优先级
flags
:保留字段
第一个参数用于指定优先级,相关定义:
/*!
* @typedef dispatch_queue_priority_t
* Type of dispatch_queue_priority
*
* @constant DISPATCH_QUEUE_PRIORITY_HIGH
* Items dispatched to the queue will run at high priority,
* i.e. the queue will be scheduled for execution before
* any default priority or low priority queue.
*
* @constant DISPATCH_QUEUE_PRIORITY_DEFAULT
* Items dispatched to the queue will run at the default
* priority, i.e. the queue will be scheduled for execution
* after all high priority queues have been scheduled, but
* before any low priority queues have been scheduled.
*
* @constant DISPATCH_QUEUE_PRIORITY_LOW
* Items dispatched to the queue will run at low priority,
* i.e. the queue will be scheduled for execution after all
* default priority and high priority queues have been
* scheduled.
*
* @constant DISPATCH_QUEUE_PRIORITY_BACKGROUND
* Items dispatched to the queue will run at background priority, i.e. the queue
* will be scheduled for execution after all higher priority queues have been
* scheduled and the system will run items on this queue on a thread with
* background status as per setpriority(2) (i.e. disk I/O is throttled and the
* thread's scheduling priority is set to lowest value).
*/
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
typedef long dispatch_queue_priority_t;
(三)自定义队列 dispatch_queue_create
声明:
/*!
* @function dispatch_queue_create
*
* @abstract
* Creates a new dispatch queue to which blocks may be submitted.
*
* @discussion
* Dispatch queues created with the DISPATCH_QUEUE_SERIAL or a NULL attribute
* invoke blocks serially in FIFO order.
*
* Dispatch queues created with the DISPATCH_QUEUE_CONCURRENT attribute may
* invoke blocks concurrently (similarly to the global concurrent queues, but
* potentially with more overhead), and support barrier blocks submitted with
* the dispatch barrier API, which e.g. enables the implementation of efficient
* reader-writer schemes.
*
* When a dispatch queue is no longer needed, it should be released with
* dispatch_release(). Note that any pending blocks submitted asynchronously to
* a queue will hold a reference to that queue. Therefore a queue will not be
* deallocated until all pending blocks have finished.
*
* Passing the result of the dispatch_queue_attr_make_with_qos_class() function
* to the attr parameter of this function allows a quality of service class and
* relative priority to be specified for the newly created queue.
* The quality of service class so specified takes precedence over the quality
* of service class of the newly created dispatch queue's target queue (if any)
* as long that does not result in a lower QOS class and relative priority.
*
* When no quality of service class is specified, the target queue of a newly
* created dispatch queue is the default priority global concurrent queue.
*
* @param label
* A string label to attach to the queue.
* This parameter is optional and may be NULL.
*
* @param attr
* A predefined attribute such as DISPATCH_QUEUE_SERIAL,
* DISPATCH_QUEUE_CONCURRENT, or the result of a call to
* a dispatch_queue_attr_make_with_* function.
*
* @result
* The newly created dispatch queue.
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_queue_t dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
第一个参数用于指定队列的 Label,可选,可以为 NULL;
第二个参数用于指定队列属性,如优先级。
第二个参数常用的选项:
-
DISPATCH_QUEUE_SERIAL
串行,值为 NULL,队列中所有任务在==同一线程==中执行 -
DISPATCH_QUEUE_CONCURRENT
并行
测试代码:
- (void)runTask:(NSString *)taskName {
NSLog(@"Start task %@", taskName);
sleep(2);
NSLog(@"End task %@", taskName);
}
- (void)testCustomQueue {
NSLog(@"开始...");
dispatch_queue_t queue = dispatch_queue_create("com.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[self runTask:@"111"];
});
dispatch_async(queue, ^{
[self runTask:@"222"];
});
dispatch_async(queue, ^{
[self runTask:@"333"];
});
}
-
DISPATCH_QUEUE_SERIAL
队列执行结果:
从执行结果可以看出,所有任务在==同一线程==中执行,但不是主线程。
2019-10-17 21:25:51.665 TestThread[49863:866207] 开始...
2019-10-17 21:25:51.666 TestThread[49863:866334] Start task 111
2019-10-17 21:25:53.671 TestThread[49863:866334] End task 111
2019-10-17 21:25:53.671 TestThread[49863:866334] Start task 222
2019-10-17 21:25:55.675 TestThread[49863:866334] End task 222
2019-10-17 21:25:55.676 TestThread[49863:866334] Start task 333
2019-10-17 21:25:57.678 TestThread[49863:866334] End task 333
-
DISPATCH_QUEUE_CONCURRENT
队列执行结果:
所有任务在不同线程中执行。
2019-10-17 21:28:04.360 TestThread[49917:868619] 开始...
2019-10-17 21:28:04.360 TestThread[49917:868691] Start task 111
2019-10-17 21:28:04.360 TestThread[49917:868689] Start task 333
2019-10-17 21:28:04.360 TestThread[49917:868718] Start task 222
2019-10-17 21:28:06.365 TestThread[49917:868718] End task 222
2019-10-17 21:28:06.365 TestThread[49917:868691] End task 111
2019-10-17 21:28:06.365 TestThread[49917:868689] End task 333
(四)任务组 dispatch_group_t
1、多线程同步任务通知
多个并行任务全部完成后,再执行最终任务:
- (void)testMultiTaskFinal {
NSLog(@"开始 group 测试...");
// 创建 Group
dispatch_group_t group = dispatch_group_create();
// 创建 队列
dispatch_queue_t queue = dispatch_queue_create("com.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
// 开始 任务 1
dispatch_group_async(group, queue, ^{
[self runTask:@"111"];
});
// 开始 任务 2
dispatch_group_async(group, queue, ^{
[self runTask:@"222"];
});
// 开始 任务 3
dispatch_group_async(group, queue, ^{
[self runTask:@"333"];
});
// 任务完成通知
dispatch_group_notify(group, queue, ^{
NSLog(@"收到任务完成通知");
// 在主线程执行善后工作
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在主线程中做 UI 刷新");
});
});
}
执行结果:
2019-10-17 21:48:01.287 TestThread[50186:886563] 开始 group 测试...
2019-10-17 21:48:01.287 TestThread[50186:886605] Start task 111
2019-10-17 21:48:01.287 TestThread[50186:886606] Start task 222
2019-10-17 21:48:01.287 TestThread[50186:886643] Start task 333
2019-10-17 21:48:03.292 TestThread[50186:886643] End task 333
2019-10-17 21:48:03.292 TestThread[50186:886605] End task 111
2019-10-17 21:48:03.292 TestThread[50186:886606] End task 222
2019-10-17 21:48:03.292 TestThread[50186:886606] 收到任务完成通知
2019-10-17 21:48:03.293 TestThread[50186:886563] 在主线程中做 UI 刷新
结论:
1、dispatch_group_notify
中的任务并不是在主线程中执行。从日志来看,是在最后一个完成任务的线程中执行。如果需要做 UI 刷新之类的任务,仍然需要指定任务在主线程中执行;
2、dispatch_group_notify
只适用于启动的线程中的任务为同步任务的场景。如果线程中的任务为异步任务,则收不到理想效果。
2、多线程异步任务通知
- (void)runAsyncTask:(NSString *)taskName
withCallback:(void(^)(NSString *))callbackBlock {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Start async task %@", taskName);
sleep(2);
NSLog(@"End async task %@", taskName);
if (callbackBlock) {
NSLog(@"Async task %@ 开始执行回调...", taskName);
callbackBlock(taskName);
NSLog(@"Async task %@ 完成执行回调", taskName);
}
});
}
- (void)testMultiAsyncTaskFinal {
NSLog(@"开始 异步任务 group 测试...");
// 创建 Group
dispatch_group_t group = dispatch_group_create();
// 定义回调块
void(^callbackBlock)(NSString *) = ^(NSString *taskName) {
NSLog(@"Async task %@ 中执行了回调", taskName);
dispatch_group_leave(group);
};
// 创建 队列
dispatch_queue_t queue = dispatch_queue_create("com.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
// 开始 异步任务 1
dispatch_group_async(group, queue, ^{
[self runAsyncTask:@"111" withCallback:callbackBlock];
});
dispatch_group_enter(group);
// 开始 异步任务 2
dispatch_group_async(group, queue, ^{
[self runAsyncTask:@"222" withCallback:callbackBlock];
});
dispatch_group_enter(group);
// 开始 异步任务 3
dispatch_group_async(group, queue, ^{
[self runAsyncTask:@"333" withCallback:callbackBlock];
});
// 任务完成通知
dispatch_group_notify(group, queue, ^{
NSLog(@"收到任务完成通知");
// 在主线程执行善后工作
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在主线程中做 UI 刷新");
});
});
}
执行结果:
2019-10-17 22:38:56.714 TestThread[50946:942540] 开始 异步任务 group 测试...
2019-10-17 22:38:56.714 TestThread[50946:942587] Start async task 111
2019-10-17 22:38:56.714 TestThread[50946:942589] Start async task 222
2019-10-17 22:38:56.714 TestThread[50946:942637] Start async task 333
2019-10-17 22:38:58.715 TestThread[50946:942589] End async task 222
2019-10-17 22:38:58.715 TestThread[50946:942587] End async task 111
2019-10-17 22:38:58.715 TestThread[50946:942637] End async task 333
2019-10-17 22:38:58.715 TestThread[50946:942589] Async task 222 开始执行回调...
2019-10-17 22:38:58.715 TestThread[50946:942587] Async task 111 开始执行回调...
2019-10-17 22:38:58.715 TestThread[50946:942637] Async task 333 开始执行回调...
2019-10-17 22:38:58.715 TestThread[50946:942589] Async task 222 中执行了回调
2019-10-17 22:38:58.715 TestThread[50946:942587] Async task 111 中执行了回调
2019-10-17 22:38:58.715 TestThread[50946:942637] Async task 333 中执行了回调
2019-10-17 22:38:58.716 TestThread[50946:942589] Async task 222 完成执行回调
2019-10-17 22:38:58.716 TestThread[50946:942587] Async task 111 完成执行回调
2019-10-17 22:38:58.716 TestThread[50946:942637] Async task 333 完成执行回调
2019-10-17 22:38:58.716 TestThread[50946:942586] 收到任务完成通知
2019-10-17 22:38:58.716 TestThread[50946:942540] 在主线程中做 UI 刷新
(五)GCD 的其他常用场景
1、只执行一次
应用场景举例:单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//
});
2、延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
});
3、信号量
使用场景举例:等待异步任务完成
- (void)runAsyncTask:(NSString *)taskName
withCallback:(void(^)(NSString *))callbackBlock {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Start async task %@", taskName);
sleep(2);
NSLog(@"End async task %@", taskName);
if (callbackBlock) {
NSLog(@"Async task %@ 开始执行回调...", taskName);
callbackBlock(taskName);
NSLog(@"Async task %@ 完成执行回调", taskName);
}
});
}
- (void)testSemaphore {
NSLog(@"开始...");
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 执行异步任务
[self runAsyncTask:@"myTask" withCallback:^(NSString *param) {
NSLog(@"回调被执行");
// 在异步任务完成的回调中,发送信号量
dispatch_semaphore_signal(semaphore);
}];
// 等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"发现任务已完成");
}
执行结果:
2019-10-17 23:48:27.559 TestThread[52611:1020523] 开始...
2019-10-17 23:48:27.559 TestThread[52611:1020586] Start async task myTask
2019-10-17 23:48:29.560 TestThread[52611:1020586] End async task myTask
2019-10-17 23:48:29.561 TestThread[52611:1020586] Async task myTask 开始执行回调...
2019-10-17 23:48:29.561 TestThread[52611:1020586] 回调被执行
2019-10-17 23:48:29.561 TestThread[52611:1020586] Async task myTask 完成执行回调
2019-10-17 23:48:29.561 TestThread[52611:1020523] 发现任务已完成
函数说明:
dispatch_semaphore_create
function dispatch_semaphore_create
用初始值创建新的计数信号量。
当两个线程需要协调特定事件的完成时,将值传递为零非常有用。 传递大于零的值对于管理有限的资源池(池的大小等于该值)很有用。
参数值
信号量的起始值。 传递小于零的值将导致返回NULL。结果
新创建的信号量,如果失败,则为NULL。
dispatch_semaphore_create
@function dispatch_semaphore_wait
@抽象
等待(减少)信号量。@讨论
减少计数信号量。 如果结果值小于零,则此函数在返回之前等待信号发生。@param dsema
信号量。 在此参数中传递NULL的结果是不确定的。@param超时
何时超时(请参阅dispatch_time)。 为方便起见,有DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。@结果
成功返回零,如果超时则返回非零。
dispatch_semaphore_signal
@function dispatch_semaphore_signal
@抽象
发信号(增加)信号量。@讨论
增加计数信号量。 如果先前的值小于零,则此函数在返回之前唤醒等待的线程。@param dsema计数信号量。
在此参数中传递NULL的结果是不确定的。@结果
如果线程被唤醒,此函数将返回非零值。 否则,返回零。
四、NSOperation
和 NSOperationQueue
(一)NSOperation
1、介绍
NSOperation
是对任务的封装,起本身是个抽象类,需要使用它的子类作为具体类来完成具体任务。NSOperation
已实现的子类包括:
-
NSInvocationOperation
用于管理调用的单个封装任务的执行 -
NSBlockOperation
管理一个或多个块的并发执行的操作
NSBlockOperation
的使用特点:- 添加到
NSBlockOperation
中的 Block 会在不同线程中并行执行 - 添加的==第一个 Block== 会在==启动 Operation 的线程==中执行
- 添加到
对于更为复杂的操作,可以自定义继承自 NSOperation
的类来完成。
2、状态
NSOperation
的状态:
- ready
- cancelled
- executiong
- finished
- asynchronous
3、任务间依赖关系
addDependency
(二)NSOperationQueue
NSOperationQueue
是任务的队列,用于控制任务的执行及关系。
常用方法:
-
addOperation
向队列中添加任务 -
setMaxConcurrentOperationCount
设置队列中任务的最大并发数
(完)