iOS中的多线程编程

关于多线程编程

什么是多线程

简单的来说,并发应用程序中一个单独的执行路径,就是一个线程。

相关术语

  • 进程:用于指代一个正在运行的可执行程序,它可以包含多个线程。
  • 任务:用于指代抽象的概念,表示需要执行工作。
  • 线程的同步:同步就是协同步调,按预定的先后次序进行运行。线程的同步即一个线程执行完指定操作后,另个一个线程再执行。
  • 线程的互斥:线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

    iOS中的多线程编程

  • pthread(POSIX threads):一套C语言接口,定义了创建和管理线程的API,非常灵活,可以直接操作线程。
  • NSThread:一套OC接口,需要自己手动管理线程,比GCD和NSOperation更加轻量级。
  • GCD:是iOS4出的一套并发编程的接口,纯C语言接口。以队列为基础。
  • NSOperation:一套面向对象的OC API,不需要手动管理线程。能实现一些GCD实现不了的功能。

NSThread

基本操作

线程的创建

  • 类方法创建线程

    1
    [NSThread detachNewThreadSelector:@selector(threadFun:) toTarget:self withObject:@"helloworld"];
  • 实例方法创建线程

    1
    2
    NSThread * th = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun:) object:@"helloworld"];
    [th start];
  • 继承NSThread,重写main方法(与NSOperation类似)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //MyThread.h
    @interface MyThread : NSThread
    @end

    //MyThread.m
    @implementation MyThread
    - (void)main {
    NSLog(@"this is MyThread");
    }

    //main.m
    #import <Foundation/Foundation.h>
    #import "MyThread.h"

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    MyThread * th = [[MyThread alloc] init];
    [th start];
    [[NSRunLoop mainRunLoop] run];
    }
    return 0;
    }
    @end
  • 其他:NSObject中的方法

    1
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

####线程的基本管理

  • 获取主线程+ (NSThread *)mainThread
  • 获取当前线程+ (NSThread *)currentThread;
  • 阻塞当前线程+ (void)sleepUntilDate:(NSDate *)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 销毁消除当前线程(currentThread)+ (void)exit;
  • 取消线程- (void)cancel;:将线程的状体置为cancelled,并不会停止线程的执行
  • 线程键的通讯
    1
    2
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

线程同步与互斥

  • @synchronized(obj)对代码段进行加锁,同时只能一个线程访问该代码段

    1
    2
    3
    4
    @synchronized(self) {
    i++;
    NSLog(@"%ld", i);
    }
  • NSLock:与@synchronized类似

    1
    2
    3
    4
    5
    6
    7
    NSLock * lock = [[NSLock alloc] init];
    if ([lock tryLock]) {
    [lock lock];
    i++;
    NSLog(@"%ld", i);
    [lock unlock];
    }
  • NSConditionLock条件锁:在所的基础上添加了条件,只有满足对应的条件,才能打开相应的锁

    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
    - (void)startTest {
    _conLock = [[NSConditionLock alloc] init];
    [NSThread detachNewThreadSelector:@selector(threadFun:) toTarget:self withObject:@"top thread"];
    for (int i = 0; i < 200; i++) {
    [NSThread detachNewThreadSelector:@selector(threadFun2:) toTarget:self withObject:@(i)];
    }
    }

    - (void)threadFun:(id)obj {

    [_conLock lock];
    NSLog(@"%@", obj);
    [NSThread sleepForTimeInterval:1.0f];
    //加锁,条件为10
    [_conLock unlockWithCondition:10];
    }

    - (void)threadFun2:(id)obj {
    NSInteger value = [obj integerValue];

    //只有到value等于10时,才能成功加锁,否则阻塞
    [_conLock lockWhenCondition:value];
    NSLog(@"thread%@", obj);
    [NSThread sleepForTimeInterval:1.0f];
    //解锁,并把条件设为value + 3,则下次上锁的条件为13,一次类推13,16,19...
    [_conLock unlockWithCondition:value + 3];
    }

输出结果:

这里写图片描述

  • NSRecursiveLock递归锁:同一个线程中,递归或多次调用加锁代码段,不会造成死锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    j = 0;
    [NSThread detachNewThreadSelector:@selector(threadFun:) toTarget:self withObject:@"top thread1"];
    [NSThread detachNewThreadSelector:@selector(threadFun:) toTarget:self withObject:@"top thread2"];

    - (void)threadFun:(id)obj {
    [self testWith:obj];
    }

    - (void)testWith:(id)obj {
    [_recLock lock];
    NSLog(@"test recursive,thread:%@, lock:%ld", obj, j++);
    [NSThread sleepForTimeInterval:1.0f];
    if (j < 10) {
    [self testWith:obj];
    }
    [_recLock unlock];
    }

输出结果:

这里写图片描述

  • NSDistributedLock分布式锁:用于多个进程之间互斥访问资源,一般用于Mac开发

条件

使用NSCondition,可以实现多线程的同步,解决类似生产者消费者问题。

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
29
30
31
32
33
34
35
36
37
38
#import "testThread.h"

@implementation testThread
{
NSInteger i;
NSCondition * _con;
NSMutableArray * _products;
}

- (void)startTest {
_con = [[NSCondition alloc] init];
_products = [[NSMutableArray alloc] init];
[NSThread detachNewThreadSelector:@selector(threadFun2:) toTarget:self withObject:@"thread2"];
[NSThread detachNewThreadSelector:@selector(threadFun2:) toTarget:self withObject:@"thread3"];
[NSThread detachNewThreadSelector:@selector(threadFun1:) toTarget:self withObject:@"thread1"];
}

- (void)threadFun1:(id)obj {
while (1) {
[NSThread sleepForTimeInterval:2.0f];
[_products addObject:@"apple"];//生产者创造一个产品
NSLog(@"%@ add one product, product count:%ld", obj, _products.count);
[_con signal];//通知消费者,消费产品
// [_con broadcast];
}
}

- (void)threadFun2:(id)obj {
while (1) {
[_con lock];
[_con wait];//等待生产者的通知
[_products removeObjectAtIndex:0];//消费者消费一个产品
NSLog(@"%@ remove one product, proudct count:%ld", obj, _products.count);
[_con unlock];
}
}

@end

GCD

基本用法

队列

  • 串行队列:同时只能执行一个任务
    • 所有任务都在同一个线程中执行,FIFO
    • 多个串行队列之间是并行执行的,会占用多个线程
    • 创建串行队列:dispatch_queue_t serialQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    • dispatch_get_main_queue();获取主线程上的一个全局可用的串行队列
  • 并行队列:同时可以执行多个任务
    • 多个任务在不同的线程上执行,最大并发数(最多起的线程数)由操作系统根据当前的系统状态决定,不可手动设置(NSOperation可以)
    • 创建并行队列:dispatch_queue_t concurrentQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    • dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);获取一个全局可用的并行队列

      任务

  • 同步任务:添加一个任务后,需要等待任务执行完后才能返回

    • 无论是串行队列还是并行队列,同步任务都是在主线程上执行的
    • 同步任务会阻塞当前线程
    • 向主队列中添加同步任务会造成死锁,见死锁①
    • 向串行队列中添加嵌套的同步任务会造成死锁,见死锁②
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      //同步任务
      dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [NSThread sleepForTimeInterval:1.0f];
      //先打印
      NSLog(@"thread:%@", [NSThread currentThread]);
      });
      NSLog(@"haha");//后打印

      //死锁①
      dispatch_sync(dispatch_get_main_queue(), ^{
      [NSThread sleepForTimeInterval:1.0f];
      NSLog(@"thread:%@", [NSThread currentThread]);
      });
      NSLog(@"haha");//造成死锁,haha永远不会打印

      //死锁②
      dispatch_queue_t serialQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
      dispatch_sync(serialQueue, ^{
      NSLog(@"thread1:%@", [NSThread currentThread]);
      dispatch_sync(serialQueue, ^{
      NSLog(@"thread2:%@", [NSThread currentThread]);//造成死锁,thread2永远不会打印
      });
      });
  • 异步任务:添加一个任务后不需要等待任务执行完成

    1
    2
    3
    4
    5
    6
    7
    //异步任务
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [NSThread sleepForTimeInterval:1.0f];
    //后打印
    NSLog(@"thread:%@", [NSThread currentThread]);
    });
    NSLog(@"haha");//先打印

其他用法

  • dispatch_after:延迟指定的时间后,将任务添加到指定的队列中

    1
    2
    3
    4
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0f * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"haha");
    });
  • dispatch_group:将多个任务或队列归为一组,在这组的所有任务都执行完之后再执行后面的操作

    • dispatch_group_async函数:创建一个指定队列中的异步任务,并将其添加到指定组中
    • dispatch_group_notify函数:指定组中所有任务都执行完后,会执行该函数所指定的的任务
    • dispatch_group_wait函数:阻塞当前线程,等待group中所有任务执行完毕
      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
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      //使用dispatch_group_notify
      dispatch_group_t group = dispatch_group_create();
      dispatch_queue_t concurrentQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"1");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"2");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"3");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"4");
      [NSThread sleepForTimeInterval:1.0f];
      });

      dispatch_group_notify(group, concurrentQueue, ^{
      NSLog(@"5");//"1234"全部打印后,才会打印5
      });

      //使用dispatch_group_wait
      dispatch_group_t group = dispatch_group_create();
      dispatch_queue_t concurrentQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"1");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"2");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"3");
      [NSThread sleepForTimeInterval:1.0f];
      });
      dispatch_group_async(group, concurrentQueue, ^{
      NSLog(@"4");
      [NSThread sleepForTimeInterval:1.0f];
      });

      dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

      NSLog(@"haha");//"dispatch_group_wait"会阻塞线程,所有haha会在"1234"之后打印
  • diapatch_once:执行一次

  • dispatch_apply:按照指定的次数将指定的Block追加到指定的DispatchQueue中,并等待全部处理执行结束

    1
    2
    3
    4
    5
    dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t t) {
    NSLog(@"%zu", t);
    [NSThread sleepForTimeInterval:1.0f];
    });
    NSLog(@"haha");//等待dispatch_apply中的所有任务都执行完之后才会带引"haha"
  • dispatch_suspend / dispatch_resume

    • dispatch_suspend:挂起指定的队列
    • dispatch_resume:恢复指定的队列
  • Dispatch Semaphore:信号量,用于排他控制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    for (int i = 0; i < 10; i++) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //排他,同时只能有一个线程进入这个代码块,相当于锁
    NSLog(@"end wait, result:%ld", result);
    [NSThread sleepForTimeInterval:1.0f];

    dispatch_semaphore_signal(semaphore);
    });
    }

NSOperation

基本用法

  • NSBlockOperation:以Block的形式定义任务

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @interface testOperation ()
    @property (nonatomic, strong) NSOperationQueue * queue;
    @end

    @implementation testOperation

    - (void)startTest {

    //创建操作
    NSOperation * oper1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"test block operation1, current thread:%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
    }];

    NSOperation * oper2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"test block operation2, current thread:%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
    }];

    NSOperation * oper3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"test block operation3, current thread:%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
    }];

    NSOperation * oper4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"test block operation4, current thread:%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
    }];

    self.queue = [[NSOperationQueue alloc] init];
    //将操作加入队列中,操作就会在队列中并发执行,NSOperationQueue默认是并发队列
    [self.queue addOperation:oper1];
    [self.queue addOperation:oper2];
    [self.queue addOperation:oper3];
    [self.queue addOperation:oper4];
    }
    @end
  • NSInvocationOperation

    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
    29
    30
    31
    32
    33
    34
    @interface testOperation ()
    @property (nonatomic, strong) NSOperationQueue * queue;
    @end

    @implementation testOperation

    - (void)startTest {

    NSMethodSignature * sig = [[self class] instanceMethodSignatureForSelector:@selector(oper1Fun:andObj2:)];
    NSInvocation * invo = [NSInvocation invocationWithMethodSignature:sig];
    [invo setTarget:self];
    [invo setSelector:@selector(oper1Fun:andObj2:)];
    NSString * arg1 = @"haha";
    NSString * arg2 = @"oooo";
    [invo setArgument:&arg1 atIndex:2];
    [invo setArgument:&arg2 atIndex:3];
    NSOperation * oper1 = [[NSInvocationOperation alloc] initWithInvocation:invo];

    NSOperation * oper2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(oper2Fun) object:nil];

    self.queue = [[NSOperationQueue alloc] init];
    [self.queue addOperation:oper1];
    [self.queue addOperation:oper2];
    }

    - (void)oper1Fun:(id)obj1 andObj2:(id)obj2 {
    NSLog(@"test invocation operation1, current thread:%@, obj1:%@, obj2:%@", [NSThread currentThread], obj1, obj2);
    [NSThread sleepForTimeInterval:1.0f];
    }

    - (void)oper2Fun {
    NSLog(@"test invocation operation2, current thread:%@", [NSThread currentThread]);
    }
    @end
  • 继承NSOperation,重写main方法

    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
    @interface MyOperation : NSOperation
    @end

    @implementation MyOperation
    - (void)main {
    NSLog(@"MyOperation, current thread:%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
    }
    @end

    //main.m
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];

    MyOperation * op1 = [[MyOperation alloc] init];
    MyOperation * op2 = [[MyOperation alloc] init];
    MyOperation * op3 = [[MyOperation alloc] init];
    MyOperation * op4 = [[MyOperation alloc] init];
    MyOperation * op5 = [[MyOperation alloc] init];
    MyOperation * op6 = [[MyOperation alloc] init];

    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
    [queue addOperation:op6];

其他用法

  • 设置最大并发数
    self.queue.maxConcurrentOperationCount = 3;
  • 设置操作之间的依赖关系
    [oper2 addDependency:oper1];//oper2依赖于oper1,即必须oper1执行完之后才能执行oper2
  • 手动管理NSOperation
    • 运行一个Operation:- (void)start;默认的start方法会直接进行一些异常判断,然后直接调用- (void)main;
    • 取消一个Operation:- (void)cancel;调用cancel方法并不会停止操作的执行,这取决于main方法中对cancel的处理。如果在main方法中没有对cancel进行处理,发送cancel消息是没有任何效果的
      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
      //MyOperation.m
      @implementation MyOperation

      - (void)start {
      [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
      }

      - (void)main {
      NSInteger index = 0;
      while (1) {
      NSLog(@"current thread:%@, index:%ld", [NSThread currentThread], index++);
      if (self.isCancelled) {
      NSLog(@"cancel");
      return;
      }
      [NSThread sleepForTimeInterval:1.0f];
      }
      }
      @end

      //main.m
      MyOperation * op1 = [[MyOperation alloc] init];
      [op1 start];
      [NSThread sleepForTimeInterval:3.0f];
      [op1 cancel];