本文内容:
多线程的优缺点
多线程实现技术方案
如何使用pThread实现多线程
如何使用NSthread执行任务、设置优先级
线程间如何通信
如何保证线程安全
iOS多线程demo地址
1.进程、线程
线程是进程的基本执行单元
进程的所有任务都在线程中执行
2.多线程在开发中的优缺点
2.1、优点
- 简化了编程模型
- 更加轻量级
- 提高了执行效率
- 提高了资源利用率
2.2缺点
- 增加了程序设计的复杂性
- 占用内存空间
- 增加了CPU的调度开销
3.多线程实现技术方案
-
pThread
:跨平台,适用于多种操作系统,可移植性强,是一套纯C语言的通用API
,线程的生命周期需要程序员自己管理,使用难度较大,开发中不常使用。 -
NSThread
:基于OC语言的API
,面向对象操作,线程的生命周期需要程序员自己管理,操作简单易用,在开发中偶尔使用。 -
GCD
:基于C语言的API
,充分利用设备多核,旨在替换NSThread
技术,线程的生命周期系统自动管理,在开发中经常使用。iOS 多线程-GCD -
NSOperation
:基于OC语言的API
,底层是GCD,增加了一些简单易用的功能,面向对象操作,线程的生命周期系统自动管理,在开发中经常使用。iOS 多线程-NSOperation + NSOperationQueue
3.1 pThread
本文只是讲解pThread的简单使用,更多讲解还请Google
引入头文件
#import <pthread.h>
创建线程
pthread_t thread;
NSString *param = @"i am pthread param";
pthread_create(&thread, NULL, run, (__bridge void *)(param));
pthread_create
有四个参数
- 代表线程
- 线程属性,设置为NULL
- 线程开启后,回调用的函数,在里面进行耗时操作
- 回调函数的参数
回调函数run
void *run (void *param){
NSString *str = (__bridge NSString *)(param);
for (int i = 0; i < 3; i ++) {
NSLog(@"i = %d,str = %@",i,str);
sleep(1);
}
return NULL;
}
运行结果
2018-12-26 14:29:37.064043+0800 Thread[3051:91506] 我在主线程执行
2018-12-26 14:29:37.064344+0800 Thread[3051:91572] i = 0,str = i am pthread param
2018-12-26 14:29:38.067615+0800 Thread[3051:91572] i = 1,str = i am pthread param
2018-12-26 14:29:39.072235+0800 Thread[3051:91572] i = 2,str = i am pthread param
:前面
的编号代表进程编号,一个APP的就是一个进程,进程编号总是一致的;
:后面
的编号代表线程的编号, 91506
代表主线程的编号,91572
代表子线程的编号,编号不一致是因为我们创建了一个线程,run
函数在子线程中执行
注意:每次运行进程、线程编号都不一样
3.2 NSthread
3.2.1生命周期
- 如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态;
- 如果CPU在运行当前线程对象的时候调用了sleep方法或者等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时或者得到同步锁,则回到就绪状态;
- 如果CPU在运行当前线程对象的时候线程任务执行完毕或者异常强制退出,则当前线程对象进入死亡状态。
3.2.2. 创建线程
创建线程有三种常用方式
-
alloc init
方式创建线程后,可以拿到线程对象,调用
start
方法启动线程,因为可以拿到线程对象,所以可以设置线程名字、优先级,优先级设置在0-1之间,优先级高,优先执行
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
//设置名字
[thread setName:@"thread"];
//设置优先级,优先级在0 - 1之间
[thread setThreadPriority:0.2];
[thread start];
-
detachNewThreadSelector
方式创建线程后,自动启动线程,这种方式拿不到线程对象
[NSThread detachNewThreadSelector:@selector(runNSthread) toTarget:self withObject:nil];
-
performSelectorInBackground
方式创建线程后,自动启动线程,这种方式拿不到线程对象
[self performSelectorInBackground:@selector(runNSthread) withObject:nil];
runNSthread
函数
- (void)runNSthread{
NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
for (int i = 0; i <= 2; i ++) {
NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
[NSThread sleepForTimeInterval:1.0];
}
}
输出结果:
2019-01-07 16:14:41.180562+0800 Thread[1769:90933] 我在主线程执行-----NSthread
2019-01-07 16:14:41.183317+0800 Thread[1769:90990] name = thread,我在子线程执行-----NSthread
2019-01-07 16:14:41.183623+0800 Thread[1769:90990] name = thread,i = 0
2019-01-07 16:14:42.185550+0800 Thread[1769:90990] name = thread,i = 1
2019-01-07 16:14:43.186270+0800 Thread[1769:90990] name = thread,i = 2
3.2.3. 线程状态
- 启动线程
- 阻塞线程
- 结束线程
- 取消线程
3.2.3.1启动线程
- (void)start;
3.2.3.2阻塞线程
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
3.2.3.3结束线程
+ (void)exit;
当到达停止条件时,线程强制退出,进入死亡状态
- (void)runNSthread{
NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
for (int i = 0; i <= 2; i ++) {
NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
if (i == 1) {
[NSThread exit];
}
}
}
输出结果:
2019-01-07 16:36:31.807896+0800 Thread[1876:99814] 我在主线程执行-----NSthread
2019-01-07 16:36:31.810076+0800 Thread[1876:99918] name = thread,我在子线程执行-----NSthread
2019-01-07 16:36:31.810366+0800 Thread[1876:99918] name = thread,i = 0
2019-01-07 16:36:31.810474+0800 Thread[1876:99918] name = thread,i = 1
3.2.3.4取消线程
- (void)cancel
调用cancel
方法,仅仅是把thread.canceled = YES;
,线程依旧会正常执行,需要搭配thread.isCancelled
属性使用,当 thread.isCancelled == YES
时,先清理线程,再中断线程。
直接调用cancel
方法
- (void)runNSthread{
NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
for (int i = 0; i <= 2; i ++) {
NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
if (i == 1) {
[[NSThread currentThread] cancel];
}
}
}
输出结果:调用cancel
方法后,继续执行
2019-01-07 16:46:25.966691+0800 Thread[1956:103567] 我在主线程执行-----NSthread
2019-01-07 16:46:25.968778+0800 Thread[1956:103723] name = thread,我在子线程执行-----NSthread
2019-01-07 16:46:25.969424+0800 Thread[1956:103723] name = thread,i = 0
2019-01-07 16:46:25.969564+0800 Thread[1956:103723] name = thread,i = 1
2019-01-07 16:46:25.969748+0800 Thread[1956:103723] name = thread,i = 2
调用cancel
方法 + thread.isCancelled
属性
- (void)runNSthread{
NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
for (int i = 0; i <= 2; i ++) {
if ([NSThread currentThread].isCancelled) {
return;
}
NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
if (i == 1) {
[[NSThread currentThread] cancel];
}
}
}
输出结果:在i==1
时,进程成功取消
2019-01-07 16:49:46.649696+0800 Thread[1989:105064] 我在主线程执行-----NSthread
2019-01-07 16:49:46.654734+0800 Thread[1989:105151] name = thread,我在子线程执行-----NSthread
2019-01-07 16:49:46.655323+0800 Thread[1989:105151] name = thread,i = 0
2019-01-07 16:49:46.657510+0800 Thread[1989:105151] name = thread,i = 1
3.2.4.线程间通信
在iOS开发工程中,我们一般在主线程中进行UI刷新,如:点击、拖拽、滚动事件,耗时操作放在其他线程中,而当耗时操作结束后,回到主线程,就需要用到线程间的通信。
创建两个线程,分别设置名字和优先级,thread2
的优先级高于thread
,thread
在i== 1
取消线程,运行结束后切回主线程
- (void)clickNSthread{
NSLog(@"我在主线程执行-----NSthread");
//1、通过alloc initc创建并执行线程,可以拿到thread对象
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
//设置名字
[thread setName:@"thread"];
//设置优先级,优先级在0 - 1之间
[thread setThreadPriority:0.2];
[thread start];
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(runNSthread) object:nil];
[thread2 setName:@"thread2"];
[thread2 setThreadPriority:0.5];
[thread2 start];
}
- (void)runNSthread{
NSLog(@"name = %@,我在子线程执行-----NSthread",[NSThread currentThread].name);
for (int i = 0; i <= 2; i ++) {
if ([NSThread currentThread].isCancelled) {
return;
}
NSLog(@"name = %@,i = %d",[NSThread currentThread].name,i);
if (i == 1 && [[NSThread currentThread].name isEqualToString:@"thread"]) {
[[NSThread currentThread] cancel];
}
if (i == 2) {
//回到主线程
[self performSelectorOnMainThread:@selector(runMianThread) withObject:nil waitUntilDone:YES];
}
}
}
- (void)runMianThread{
NSLog(@"回到主线程执行-----NSthread");
}
输出结果:
-
thread2
和thread
分别开启了一个线程, -
thread2
优先于thread
执行,因为优先级高 -
thread
在i== 1
后,线程取消 -
thread2
任务完成后回到主线程,完成线程间通信
2019-01-07 16:57:45.736291+0800 Thread[2065:108373] 我在主线程执行-----NSthread
2019-01-07 16:57:45.737050+0800 Thread[2065:108435] name = thread2,我在子线程执行-----NSthread
2019-01-07 16:57:45.737657+0800 Thread[2065:108435] name = thread2,i = 0
2019-01-07 16:57:45.738507+0800 Thread[2065:108435] name = thread2,i = 1
2019-01-07 16:57:45.739965+0800 Thread[2065:108435] name = thread2,i = 2
2019-01-07 16:57:45.741878+0800 Thread[2065:108373] 回到主线程执行-----NSthread
2019-01-07 16:57:45.745420+0800 Thread[2065:108434] name = thread,我在子线程执行-----NSthread
2019-01-07 16:57:45.745743+0800 Thread[2065:108434] name = thread,i = 0
2019-01-07 16:57:45.745879+0800 Thread[2065:108434] name = thread,i = 1
相关方法:
//在主线程调用
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//在子线程中调用
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
3.2.5.线程安全与线程同步
线程安全
:在多个线程中同时访问并操作同一对象时,运行结果与预期的值相同就是线程安全。
线程安全问题都是由全局变量
及静态变量
引起的,若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步
:可理解为线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
实现线程安全需要加锁,加锁方式有很多种,深入理解iOS开发中的锁,请点击这里
这里主要讲三种方式
-
synchronized
; -
NSLock
、NSConditionLock
、NSRecursiveLock
、NSCondition
加锁方式都差不多,都是实现NSLocking
协议,调用- (void)lock;
和- (void)unlock;
实现线程安全; -
dispatch_semaphore
(在GCD文章中有详细解释)。
例子:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。创建一个TicketManager
,在外面调用startToSale
方法
#import "TicketManager.h"
#define Total 50
@interface TicketManager ()
@property (nonatomic,assign) NSInteger tickets;//剩余票数
@property (nonatomic,assign) NSInteger saleCount;//卖出票数
@property (nonatomic,strong) NSThread *threadBJ;//北京票点
@property (nonatomic,strong) NSThread *threadSH;//上海票点
/*
NSLock、NSConditionLock、NSRecursiveLock、NSCondition加锁方式都一样,都是实现NSLocking协议
*/
@property (nonatomic,strong) NSCondition *condition;
@property (nonatomic , strong) dispatch_semaphore_t semaphore;
@end
@implementation TicketManager
- (instancetype)init{
self = [super init];
if (self) {
_condition = [[NSCondition alloc]init];
_semaphore = dispatch_semaphore_create(1);
_tickets = Total;
_threadBJ = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
_threadSH = [[NSThread alloc]initWithTarget:self selector:@selector(sale) object:nil];
[_threadBJ setName:@"北京"];
[_threadSH setName:@"上海"];
}
return self;
}
- (void)sale{
while (1) {
//1、synchronized
@synchronized (self) {
if (self.tickets > 0 ) {
[NSThread sleepForTimeInterval:0.1];
self.tickets --;
self.saleCount = Total - self.tickets;
NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
}else{
break;//一定要break,不然就会死循环
}
}
// 2、NSCondition
// [self.condition lock];
// if (self.tickets > 0 ) {
// [NSThread sleepForTimeInterval:0.1];
// self.tickets --;
// self.saleCount = Total - self.tickets;
// NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
// }else{
// break;
// }
// [self.condition unlock];
//
//3、dispatch_semaphore方式
// dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// if (self.tickets > 0 ) {
// [NSThread sleepForTimeInterval:0.1];
// self.tickets --;
// self.saleCount = Total - self.tickets;
// NSLog(@"%@ , 卖出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
// }else{
// dispatch_semaphore_signal(self.semaphore);
// break;
// }
// dispatch_semaphore_signal(self.semaphore);
}
}
- (void)startToSale{
[_threadSH start];
[_threadBJ start];
}
@end
如果不加锁的话,则屏蔽掉加锁代码 @synchronized (self) {}
输出结果:出现资源抢夺问题,票数卖出不正常
2019-01-07 17:22:32.606457+0800 Thread[2261:116831] 上海 , 卖出 = 1,剩余= 49
2019-01-07 17:22:32.606440+0800 Thread[2261:116832] 北京 , 卖出 = 1,剩余= 49
2019-01-07 17:22:32.710909+0800 Thread[2261:116831] 上海 , 卖出 = 2,剩余= 48
2019-01-07 17:22:32.710910+0800 Thread[2261:116832] 北京 , 卖出 = 2,剩余= 48
2019-01-07 17:22:32.812270+0800 Thread[2261:116832] 北京 , 卖出 = 4,剩余= 46
2019-01-07 17:22:32.812469+0800 Thread[2261:116831] 上海 , 卖出 = 3,剩余= 47
加锁后输出结果:没有资源抢夺的问题,票数正常卖出。
018-12-29 16:06:21.096548+0800 Thread[16887:391801] 上海 , 卖出 = 1,剩余= 49
2018-12-29 16:06:21.197614+0800 Thread[16887:391802] 北京 , 卖出 = 2,剩余= 48
2018-12-29 16:06:21.301553+0800 Thread[16887:391801] 上海 , 卖出 = 3,剩余= 47
2018-12-29 16:06:21.407615+0800 Thread[16887:391802] 北京 , 卖出 = 4,剩余= 46
2018-12-29 16:06:21.509136+0800 Thread[16887:391801] 上海 , 卖出 = 5,剩余= 45
2018-12-29 16:06:21.613467+0800 Thread[16887:391802] 北京 , 卖出 = 6,剩余= 44
2018-12-29 16:06:21.715522+0800 Thread[16887:391801] 上海 , 卖出 = 7,剩余= 43
2018-12-29 16:06:21.819181+0800 Thread[16887:391802] 北京 , 卖出 = 8,剩余= 42
2018-12-29 16:06:21.921224+0800 Thread[16887:391801] 上海 , 卖出 = 9,剩余= 41
2018-12-29 16:06:22.027030+0800 Thread[16887:391802] 北京 , 卖出 = 10,剩余= 40
2018-12-29 16:06:22.130868+0800 Thread[16887:391801] 上海 , 卖出 = 11,剩余= 39
2018-12-29 16:06:22.233431+0800 Thread[16887:391802] 北京 , 卖出 = 12,剩余= 38
2018-12-29 16:06:22.337925+0800 Thread[16887:391801] 上海 , 卖出 = 13,剩余= 37
2018-12-29 16:06:22.439501+0800 Thread[16887:391802] 北京 , 卖出 = 14,剩余= 36
2018-12-29 16:06:22.542079+0800 Thread[16887:391801] 上海 , 卖出 = 15,剩余= 35
2018-12-29 16:06:22.647700+0800 Thread[16887:391802] 北京 , 卖出 = 16,剩余= 34
2018-12-29 16:06:22.752251+0800 Thread[16887:391801] 上海 , 卖出 = 17,剩余= 33
2018-12-29 16:06:22.855627+0800 Thread[16887:391802] 北京 , 卖出 = 18,剩余= 32
2018-12-29 16:06:22.960312+0800 Thread[16887:391801] 上海 , 卖出 = 19,剩余= 31
2018-12-29 16:06:23.065476+0800 Thread[16887:391802] 北京 , 卖出 = 20,剩余= 30
2018-12-29 16:06:23.166326+0800 Thread[16887:391801] 上海 , 卖出 = 21,剩余= 29
2018-12-29 16:06:23.270858+0800 Thread[16887:391802] 北京 , 卖出 = 22,剩余= 28
2018-12-29 16:06:23.373545+0800 Thread[16887:391801] 上海 , 卖出 = 23,剩余= 27
2018-12-29 16:06:23.477072+0800 Thread[16887:391802] 北京 , 卖出 = 24,剩余= 26
2018-12-29 16:06:23.577959+0800 Thread[16887:391801] 上海 , 卖出 = 25,剩余= 25
2018-12-29 16:06:23.682754+0800 Thread[16887:391802] 北京 , 卖出 = 26,剩余= 24
2018-12-29 16:06:23.787569+0800 Thread[16887:391801] 上海 , 卖出 = 27,剩余= 23
2018-12-29 16:06:23.892799+0800 Thread[16887:391802] 北京 , 卖出 = 28,剩余= 22
2018-12-29 16:06:23.995028+0800 Thread[16887:391801] 上海 , 卖出 = 29,剩余= 21
2018-12-29 16:06:24.099468+0800 Thread[16887:391802] 北京 , 卖出 = 30,剩余= 20
2018-12-29 16:06:24.202840+0800 Thread[16887:391801] 上海 , 卖出 = 31,剩余= 19
2018-12-29 16:06:24.307683+0800 Thread[16887:391802] 北京 , 卖出 = 32,剩余= 18
2018-12-29 16:06:24.410991+0800 Thread[16887:391801] 上海 , 卖出 = 33,剩余= 17
2018-12-29 16:06:24.514261+0800 Thread[16887:391802] 北京 , 卖出 = 34,剩余= 16
2018-12-29 16:06:24.621852+0800 Thread[16887:391801] 上海 , 卖出 = 35,剩余= 15
2018-12-29 16:06:24.727306+0800 Thread[16887:391802] 北京 , 卖出 = 36,剩余= 14
2018-12-29 16:06:24.830832+0800 Thread[16887:391801] 上海 , 卖出 = 37,剩余= 13
2018-12-29 16:06:24.934031+0800 Thread[16887:391802] 北京 , 卖出 = 38,剩余= 12
2018-12-29 16:06:25.036285+0800 Thread[16887:391801] 上海 , 卖出 = 39,剩余= 11
2018-12-29 16:06:25.141081+0800 Thread[16887:391802] 北京 , 卖出 = 40,剩余= 10
2018-12-29 16:06:25.244907+0800 Thread[16887:391801] 上海 , 卖出 = 41,剩余= 9
2018-12-29 16:06:25.349845+0800 Thread[16887:391802] 北京 , 卖出 = 42,剩余= 8
2018-12-29 16:06:25.453208+0800 Thread[16887:391801] 上海 , 卖出 = 43,剩余= 7
2018-12-29 16:06:25.557792+0800 Thread[16887:391802] 北京 , 卖出 = 44,剩余= 6
2018-12-29 16:06:25.660827+0800 Thread[16887:391801] 上海 , 卖出 = 45,剩余= 5
2018-12-29 16:06:25.763069+0800 Thread[16887:391802] 北京 , 卖出 = 46,剩余= 4
2018-12-29 16:06:25.867769+0800 Thread[16887:391801] 上海 , 卖出 = 47,剩余= 3
2018-12-29 16:06:25.969181+0800 Thread[16887:391802] 北京 , 卖出 = 48,剩余= 2
2018-12-29 16:06:26.071585+0800 Thread[16887:391801] 上海 , 卖出 = 49,剩余= 1
2018-12-29 16:06:26.176595+0800 Thread[16887:391802] 北京 , 卖出 = 50,剩余= 0
参考文章:
iOS多线程慕课网视频
NSThread总结
iOS 多线程:『pthread、NSThread』详尽总结
深入理解iOS开发中的锁
文章链接:
iOS 多线程-GCD
iOS 多线程-NSOperation + NSOperationQueue
喜欢就点个赞吧✌️✌️
有错之处,还请指出,感谢🙏🙏