iOS 多线程基础知识整理

iOS 多线程基础知识整理_第1张图片
iOS 多线程基础知识整理_第2张图片

本笔记整理自 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的结果是不确定的。

@结果
如果线程被唤醒,此函数将返回非零值。 否则,返回零。

四、NSOperationNSOperationQueue

(一)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 设置队列中任务的最大并发数

(完)

你可能感兴趣的:(iOS 多线程基础知识整理)