ios多线程开发的常用四种方式 (附有demo)
1. pthread
2. NSThread
3. NSOperation\NSOperationQueue
4. GCD

一 、pthread

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

代码实现

//创建线程
 NSLog(@"开始执行");
 int pthread_create(pthread_t * __restrict ,const pthread_attr_t * __restrict ,void *(*)(void *),void * __restrict);
 //使用
 pthread_t pthread;
 pthread_create(&pthread, NULL, function, NULL);
 NSLog(@"执行结束");

void * function(void * param) {
    for (int i = 0; i < 5; i ++) {
        NSLog(@"i : %d-- 线程: %@",i,[NSThread currentThread]);
    }
    return NULL;
}

运行结果 :

2018-11-22 11:49:57.045504+0800 Multithreading[20231:4407861] 开始执行
2018-11-22 11:49:57.045716+0800 Multithreading[20231:4407861] 执行结束
2018-11-22 11:49:57.045828+0800 Multithreading[20231:4409725] i : 0–
线程: <NSThread: 0x6080002724c0>{number = 3, name = (null)} 2018-11-22
11:49:57.046230+0800 Multithreading[20231:4409725] i : 1-- 线程:
<NSThread: 0x6080002724c0>{number = 3, name = (null)} 2018-11-22
11:49:57.047038+0800 Multithreading[20231:4409725] i : 2-- 线程:
<NSThread: 0x6080002724c0>{number = 3, name = (null)} 2018-11-22
11:49:57.047152+0800 Multithreading[20231:4409725] i : 3-- 线程:
<NSThread: 0x6080002724c0>{number = 3, name = (null)} 2018-11-22
11:49:57.047439+0800 Multithreading[20231:4409725] i : 4-- 线程:
<NSThread: 0x6080002724c0>{number = 3, name = (null)}

二、 NSThread
创建NSThread 有三种方法

//公共方法
- (void)threadMoth {
    NSLog(@"%@",[NSThread currentThread]);
}

// 方法 1 需要手动开启
NSThread * thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMoth) object:nil];
[thread start];
//方法 2
[NSThread detachNewThreadSelector:@selector(threadMoth) toTarget:self withObject:nil];
//方法 3
[self performSelectorInBackground:@selector(threadMoth) withObject:nil];

//常用的相关方法

[NSThread mainThread];// 获得主线程
[NSThread isMainThread]; //是否为主线程
NSLog(@"shuchu--%@--%d",[NSThread mainThread],[NSThread isMainThread]);
NSLog(@"休眠前");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4.0]];//阻塞线程 以当前时间为准阻塞4秒
NSLog(@"休眠1");
[NSThread sleepForTimeInterval:2];//阻塞线程 2 秒
NSLog(@"休眠后");
[NSThread exit];// 强制退出线程

方法2、一调用就会立即创建一个线程来做事情;
方法1、需要手动调用 start 启动线程时才会真正去创建线程。
方法3、是利用NSObject的方法 performSelectorInBackground:withObject: 来创建一个线程:在后台运行某一个方法
与 NSThread 的 detachNewThreadSelector:toTarget:withObject: 是一样的。

运行结果

2018-11-22 16:17:36.959316+0800 Multithreading[20834:4901061]
<NSThread: 0x60c00007b800>{number = 4, name = (null)} 2018-11-22
16:17:36.959318+0800 Multithreading[20834:4901060] <NSThread:
0x60c00007b8c0>{number = 3, name = (null)} 2018-11-22
16:17:36.960902+0800 Multithreading[20834:4901062] <NSThread:
0x60c00007bc40>{number = 5, name = (null)}

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

(1)、NSOperation
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有两种方式来封装操作。

  • 子类 NSInvocationOperation
  • 子类 NSBlockOperation

1、 子类 NSInvocationOperation

  • 创建 NSInvocationOperation 对象
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadMoth) object:nil];
  • 调用 start 方法开始执行操作
[operation start];

运行结果

2018-11-22 16:17:36.959446+0800 Multithreading[20834:4899673]
<NSThread: 0x600000075840>{number = 1, name = main}

通过运行结果 可以看到 在没有加入到NSOperationQueue中,而是单独使用的时候,NSInvocationOperation
并没有开启新的线程,还是在主线程中执行的

2、子类NSBlockOperation

  • 创建对象
NSBlockOperation * block = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@"block-%@",[NSThread currentThread]);
}];
  • 添加额外的操作
[block addExecutionBlock:^{
     NSLog(@"block-%@",[NSThread currentThread]);// 打印当前线程
}];
  • 调用 start 方法开始执行操作
[block start];

运行结果:

2018-11-22 16:41:36.057252+0800 Multithreading[20893:4948069]
<NSThread: 0x600000074a00>{number = 1, name = main}

和NSInvocationOperation 使用一样。因为代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。 通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。

(2)、NSOperationQueue

NSOperation 需要配合 NSOperationQueue 来实现多线程, 需要将创建好的操作加入到队列中去。总共有两种方法:

第一种方法:

- (void)addOperation:(NSOperation *)op;

需要先创建操作,再将创建好的操作加入到创建好的队列中去。代码实现

//创建队列
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
//添加操作
NSInvocationOperation * operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadMoth) object:nil];
NSInvocationOperation * operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadMoth) object:nil];
NSBlockOperation * block1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"block1-%@",[NSThread currentThread]);// 打印当前线程
}];
[block1 addExecutionBlock:^{
    NSLog(@"block2-%@",[NSThread currentThread]);// 打印当前线程
}];
//3 添加队列
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:block1];

运行结果

2018-11-22 17:27:58.671071+0800 Multithreading[23002:5205701]
<NSThread: 0x600000262ac0>{number = 3, name = (null)}
2018-11-22 17:27:58.671073+0800 Multithreading[23002:5205666] queue1-<NSThread: 0x60c000265a80>{number = 5, name = (null)}
2018-11-22 17:27:58.671071+0800 Multithreading[23002:5205664] block1-<NSThread: 0x608000260400>{number = 6, name = (null)}
2018-11-22 17:27:58.671115+0800 Multithreading[23002:5205663] <NSThread: 0x608000260340>{number = 4, name = (null)}

使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到操作队列后能够开启新线程,进行并发执行。

第二种方法:

- (void)addOperationWithBlock:(void (^)(void))block;

无需创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

NSOperationQueue * queue1 = [[NSOperationQueue alloc] init];
[queue1 addOperationWithBlock:^{
    NSLog(@"queue1-%@",[NSThread currentThread]);// 打印当前线程
}];
[queue1 addOperationWithBlock:^{
    NSLog(@"queue1-%@",[NSThread currentThread]);// 打印当前线程
}];
[queue1 addOperationWithBlock:^{
    NSLog(@"queue1-%@",[NSThread currentThread]);// 打印当前线程
}];
[queue1 addOperationWithBlock:^{
    NSLog(@"queue1-%@",[NSThread currentThread]);// 打印当前线程
}];

运行结果

2018-11-22 17:27:58.671156+0800 Multithreading[23002:5205665]
queue1-<NSThread: 0x6000002613c0>{number = 7, name = (null)}
2018-11-22 17:27:58.671322+0800 Multithreading[23002:5205664] block2-<NSThread: 0x608000260400>{number = 6, name = (null)}
2018-11-22 17:27:58.671331+0800 Multithreading[23002:5205666] queue1-<NSThread: 0x60c000265a80>{number = 5, name = (null)}
2018-11-22 17:27:58.671367+0800 Multithreading[23002:5205701] queue1-<NSThread: 0x600000262ac0>{number = 3, name = (null)}

使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。

四、 GCD

GCD会自动利用更多的CPU内核,会自动管理线程的生命周期,不需要写管理线程的代码。定制任务 将任务添加到队列中 GCD会自动将队列中的任务取出,放到线程中去执行,任务的取出遵循FIFO原则。

  1. Main queue:
      运行在主线程,由dispatch_get_main_queue获得.和ui相关的就要使用MainQueue,在主线程中更新ui.
  2. Serial quque(private dispatch queue)
      每次运行一个任务,可以添加多个,执行次序FIFO.
  3. Concurrent queue(globaldispatch queue):
    可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.使用dispatch_get_global_queue获得.
同步 在当前线程中执行
 dispatch_sync(dispatch_queue_t queue, ^(void)block)
 异步 可以在新的线程中执行,有开新线程的能力(不是一定会开新线程,比如放在主队列中)
 dispatch_async(dispatch_queue_t queue, ^(void)block)

常用地方

//延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"延时2秒执行");
});

//全局异步并发队列
/**
 参数 identifier 优先级
 参数 flags      保留参数 默认传0
 */
dispatch_queue_t queuet = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queuet, ^{
    NSLog(@"queuet --- 1");
});
dispatch_async(queuet, ^{
    NSLog(@"queuet --- 2");
});
dispatch_async(queuet, ^{
    NSLog(@"queuet --- 3");
});

//自定义并发队列
dispatch_queue_t queuet1 = dispatch_queue_create("www.baidu.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queuet1, ^{
    NSLog(@"queuet1 --- 1");
});
dispatch_async(queuet1, ^{
    NSLog(@"queuet1 --- 2");
});
dispatch_async(queuet1, ^{
    NSLog(@"queuet1 --- 3");
});

dispatch_queue_t queuet2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queuet2, ^{
    NSLog(@"queuet2 --- 1");
});
dispatch_barrier_async(queuet2, ^{
    NSLog(@"queuet2 --- 2");
});

//group
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"group1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"group2");
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"group3");
});


//创建单例
static dispatch_once_t onceToken;
static ViewController * viewController;
dispatch_once(&onceToken, ^{
    viewController = [[ViewController alloc] init];
});
return viewController;

五、线程安全问题
资源共享:
一块资源可能会被多个线程共享,比如多个线程访问同一个对象、对一个变量、同一个文件

解决方法:
 互斥锁
 @synchronized(锁对象){ //需要锁定的代码 }
 或者 加NSLock锁
 
 原子和非原子属性
 atomic 默认 原子属性 为setter方法加锁
 线程安全,需要消耗大量的资源
 nonatomic 非原子属性 不会为setter方法加锁
 非线程安全,适合内存小的移动设备
 
 注意:
 所有属性都声明为nonatomic
 尽量避免多线程抢夺同一块资源
 尽量加加锁、资源抢夺的业务逻辑交给服务器处理,减小移动客户端压力


GitHub demo 下载