一 多线程概念
进程:系统中正在运行的一个应用程序.一个app就是一个进程.像我们进入后台划走的操作就是 杀掉进程,每个进程都是独立的.
线程:一个进程可以开启多个线程,线程是可以并行执行不同任务的,多线程可以充分的利用多核cpu来处理任务
关系
1.同一个进程内的所有线程共享本进程的地址空间,共享本进程的资源,比如内存 cpu等,而进程之间则是独立的地址空间
2.一个进程奔溃后,在保护模式下不会对其他进程产生影响,而一个线程奔溃后会导致整个进程奔溃
线程的优缺点
1.能适当提高程序的执行效果
2.能适当提高资源的利用率
3.线程上的任务执行结束后,线程会自动销毁
缺点:
开启线程是需要占用一定的内存空间的,开启的线程越多,cpu在调用上开销就越大.而且程序设计更加的复杂.线程之间的通信,多线程数据的共享
多线程的实现方式
thread : 可以直接操作线程对象,线程生命周期由程序员管理
NSOperation 给予GCD,线程生命周期系统自动管理
GCD: 一套改进的c语言多线程Api,能充分利用设备的多和优势
NSThread
我们先来看创建的方式
NSThread *thread1=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程一"];
thread1.name =@"线程一";
[thread1 start];
//自动启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"线程2"];
//隐式启动 后台异步执行
[self performSelectorInBackground:@selector(run:) withObject:@"线程三"];
-(void)run:(NSString *)threadStr{
/*
isMainThread 判断是否为主线程
currentThread 获取当前程序
*/
if (![[NSThread currentThread] isMainThread]) {
NSLog(@"这不是主线程");
}
if ([threadStr isEqualToString:@"线程一"]) {
NSLog(@"thread = %@",[NSThread currentThread]);
} else if ([threadStr isEqualToString:@"线程2"]) {
NSLog(@"thread = %@",[NSThread currentThread]);
} else{
NSLog(@"thread = %@",[NSThread currentThread]);
}
[[NSThread currentThread] cancel];
}
其他相关的方法
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
//指定某一个线程执行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
//主线程实现
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
下面来个具体的方法,子线程实现耗时操作,在执行完之后回到主线程
/模拟子线程耗时操作,完成之后回到主线程刷新UI
NSThread *subthread=[[NSThread alloc]initWithTarget:self selector:@selector(runtestSub) object:@"子线程"];
[subthread start];
//延迟两秒执行
[self performSelector:@selector(run:) withObject:@"线程4" afterDelay:2.0f];
//指定subthread线程上执行
[self performSelector:@selector(runtestSub) onThread:subthread withObject:nil waitUntilDone:NO];
-(void)runtestSub{
NSLog(@"thread = %@",[NSThread currentThread]);
sleep(4);
[self performSelectorOnMainThread:@selector(mainUI) withObject:nil waitUntilDone:NO];
NSLog(@"执行完主线程了");
[[NSThread currentThread] cancel];
}
-(void)mainUI{
NSLog(@"这是主线程吧 thread = %@",[NSThread currentThread]);
sleep(3);
}
NSThread日常开发中常用的也就这些.下面重点来看一下GCD,这个在开发中用的可就太多了.
GCD
了解GCD,你必须先了解两个概念,队列和任务.
同步执行 :同步添加到当前的队列中,在添加的任务结束之前,会一直等待,只能在当前线程中执行任务,不具备开区新线程的能力
异步执行 :不会做任务等待,可以继续执行任务. 在新的线程中执行任务,具备开启新线程的能力
串行队列 :让任务一个接一个的执行
并发队列:可以让多个任务同事执行. 并发队列只有在异步(async)方法下才有效
3.经典各种组合模式
了解了队列任务的概念,就有各种组合.我们分别一个一个的来实现一下.
1.同步+串行:没有开启新线程,串行执行任务
//同步+串行
-(void)gcdtest{
dispatch_queue_t queue = dispatch_queue_create("Serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"thread线程是--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
});
}
结果:2022-05-10 10:11:52.919600+0800 多线程demo[10086:447048] thread线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}
2022-05-10 10:11:52.919767+0800 多线程demo[10086:447048] thread 2线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}
2022-05-10 10:11:52.919923+0800 多线程demo[10086:447048] thread 3线程是--<_NSMainThread: 0x60000067c8c0>{number = 1, name = main}
总结:可以看到是按顺序执行的,由于是同步任务,并没有开辟新的子线程,所以是在主线程上面执行的.
2.异步+串行:有开启新线程(1条),串行执行任务
-(void)gcdtest1{
dispatch_queue_t seruqueue = dispatch_queue_create("serqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(seruqueue, ^{
NSLog(@"thread线程是--%@",[NSThread currentThread]);
});
dispatch_async(seruqueue, ^{
NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
});
dispatch_async(seruqueue, ^{
NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
});
}
结果:2022-05-10 10:16:27.454716+0800 多线程demo[10139:450303] thread线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}
2022-05-10 10:16:27.454834+0800 多线程demo[10139:450303] thread 2线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}
2022-05-10 10:16:27.454923+0800 多线程demo[10139:450303] thread 3线程是--<NSThread: 0x600002f0c800>{number = 6, name = (null)}
开启了一条新线程,但由于是串行队列所以还是得一个一个的执行.
3 异步+并发:有开启新线程(多条),并发执行任务
-(void)gcdtest2{
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"thread线程是--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"thread 2线程是--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"thread 3线程是--%@",[NSThread currentThread]);
});
}
打印结果:2022-05-10 10:23:03.398415+0800 多线程demo[10208:454142] thread线程是--<NSThread: 0x600001fec040>{number = 5, name = (null)}
2022-05-10 10:23:03.398415+0800 多线程demo[10208:454141] thread 2线程是--<NSThread: 0x600001fd8000>{number = 6, name = (null)}
2022-05-10 10:23:03.398415+0800 多线程demo[10208:454146] thread 3线程是--<NSThread: 0x600001ffe880>{number = 4, name = (null)}
当异步并发执行的时候,开启了多个子线程.并且执行的顺序是不固定的.
4 主线程+同步
-(void)gcdtest3{
dispatch_queue_t mainqueue = dispatch_get_main_queue();
dispatch_sync(mainqueue, ^{
NSLog(@"奔溃了");
});
}
总结:会发现,项目直接奔溃了.那么奔溃的原因是什么呢?这是由于,在主线程上,先执行viewdidload,然后执行gcdtest3中的事件.此时主线程是等待gcdtest3执行完的,而这个时候,我们把gcdtest3中的事件加到了主线程上,现在viewdidload和 NSLog(@"奔溃了");变成了同一级.gcdtest3中的方法一直没法执行,造成的结果就是viewdidload在等待gcdtest3执行完,而 NSLog(@"奔溃了");则在等待vivididload执行完.所以直接就卡死了.两个相互等待.直接奔溃了.
那要怎么解决呢,把同步变为异步,就可以.
看了上面的各个组合的实现,其实我们把队列和任务来个比喻,方便更好的理解.
假设有一个患者打针看病的场景,
如果是串行同步的,那就是有一个医生,然后各个患者按照顺讯排队一个一个来.
异步串行又是什么呢?它就好比是:患者都排成了一排,但是这个时候医生就只有一个,所以看病的顺序还是要按先来后到的一个一个的来
同步并发呢?这个恰恰相反了,现在医生有好多个了,但是患者是一个一个的排成一列了.只能一个看完另外一个才能接着看病.
异步并发: 患者排成一排,然后医生也有多个,一个对应一个患者,同时开始看病
4 GCD线程间的通信
在子线程执行耗时操作,执行完成之后回到主线程去刷新UI
-(void)GCDrefreshUI{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"执行耗时操作");
sleep(4);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"查看当前的线程:--%@",[NSThread currentThread]);
});
});
}
5 GCD其他常见的方法
1 栅栏方法(dispatch_barrier_async)
我们有时需要异步执行两组操作,第一组操作执行完之后,采取执行第二组操作.这个时候我们就需要用到栅栏方法(dispatch_barrier_async)
-(void)barray_gcdtest{
//无法拦截全局队列中,只能是自定义的
dispatch_queue_t serqueue = dispatch_queue_create("serqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(serqueue, ^{
NSLog(@" 1-线程:--%@",[NSThread currentThread]);
});
dispatch_async(serqueue, ^{
NSLog(@"2-线程:--%@",[NSThread currentThread]);
});
//添加栅栏
dispatch_barrier_async(serqueue, ^{
sleep(1);
NSLog(@"当前线程:--%@",[NSThread currentThread]);
});
dispatch_async(serqueue, ^{
NSLog(@"3-线程:--%@",[NSThread currentThread]);
});
dispatch_async(serqueue, ^{
NSLog(@"4-线程:--%@",[NSThread currentThread]);
});
dispatch_async(serqueue, ^{
NSLog(@"5-线程:--%@",[NSThread currentThread]);
});
}
打印结果:2022-05-10 10:56:46.368343+0800 多线程demo[10542:472778] 2-线程:--<NSThread: 0x6000039f8080>{number = 7, name = (null)}
2022-05-10 10:56:46.368346+0800 多线程demo[10542:472780] 1-线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371021+0800 多线程demo[10542:472780] 当前线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371462+0800 多线程demo[10542:472780] 3-线程:--<NSThread: 0x6000039b9380>{number = 6, name = (null)}
2022-05-10 10:56:47.371487+0800 多线程demo[10542:472778] 4-线程:--<NSThread: 0x6000039f8080>{number = 7, name = (null)}
2022-05-10 10:56:47.371547+0800 多线程demo[10542:472782] 5-线程:--<NSThread: 0x6000039a4080>{number = 4, name = (null)}
如果没有dispatch_barrier_async,执行结果应该是无序的.但加入dispatch_barrier_async以后,就相当于把1 2扒拉到一边了,剩下的是另外一个,先执行1 2这组,然后其余的这组.同时还有一个注意的点,只有在自定义的队列中才能 栅栏才是有效果的.如果是一个全局队列是无法拦截的.
2 group组
当我们想知道异步+并发,什么时候执行完的时候,就需要用到dispatch_group_t.把要监测的线程放到group中,然后有一个dispatch_group_notify方法,通知执行完成.
-(void)group_gcdtest{
dispatch_group_t testGroup = dispatch_group_create();
dispatch_queue_t glopeQueue = dispatch_get_global_queue(0, 0);
dispatch_group_async(testGroup, glopeQueue, ^{
sleep(1);
NSLog(@"线程1 -%@",[NSThread currentThread]);
});
dispatch_group_async(testGroup, glopeQueue, ^{
sleep(4);
NSLog(@"线程2 -%@",[NSThread currentThread]);
});
dispatch_group_async(testGroup, glopeQueue, ^{
sleep(3);
NSLog(@"线程3 -%@",[NSThread currentThread]);
});
dispatch_group_notify(testGroup, glopeQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主线程 -%@",[NSThread currentThread]);
});
});
}
打印结果:2022-05-10 11:21:44.719382+0800 多线程demo[10756:484771] 线程1 -<NSThread: 0x6000029ec3c0>{number = 7, name = (null)}
2022-05-10 11:21:46.716449+0800 多线程demo[10756:484772] 线程3 -<NSThread: 0x6000029e5180>{number = 5, name = (null)}
2022-05-10 11:21:47.716715+0800 多线程demo[10756:484776] 线程2 -<NSThread: 0x60000298c240>{number = 6, name = (null)}
2022-05-10 11:21:47.717220+0800 多线程demo[10756:484556] 主线程 -<_NSMainThread: 0x6000029e8a80>{number = 1, name = main}
从打印的结果就可以发现,线程都执行完以后,会调用notyf的方法.
3 快速迭代(dispatch_apply)
通常我们会使用for循环遍历,GCD提供了一个dispatch_apply.它可以按照指定的次数将任务追加到指定的队列中,并等贷全部任务执行结束. 如果实在串行队列中使用,就和for循环一样,是按循序执行的.所以实际使用的时候一般是并发队列
//快速迭代 遍历
-(void)apply_gcdtest{
dispatch_queue_t globequeue = dispatch_get_global_queue(0, 0);
NSLog(@"开始迭代");
dispatch_apply(6, globequeue, ^(size_t iteration) {
sleep(1);
NSLog(@"线程- %ld = %@",iteration,[NSThread currentThread]);
});
NSLog(@"迭代完成");
}
结果:2022-05-10 11:32:44.019881+0800 多线程demo[10852:490803] 开始迭代
2022-05-10 11:32:45.020560+0800 多线程demo[10852:490803] 线程- 0 = <_NSMainThread: 0x6000013a08c0>{number = 1, name = main}
2022-05-10 11:32:45.020557+0800 多线程demo[10852:490870] 线程- 1 = <NSThread: 0x6000013ae700>{number = 7, name = (null)}
2022-05-10 11:32:45.020556+0800 多线程demo[10852:490869] 线程- 3 = <NSThread: 0x6000013f8300>{number = 5, name = (null)}
2022-05-10 11:32:45.020562+0800 多线程demo[10852:490866] 线程- 4 = <NSThread: 0x6000013a0580>{number = 8, name = (null)}
2022-05-10 11:32:45.020562+0800 多线程demo[10852:490864] 线程- 2 = <NSThread: 0x6000013e1d80>{number = 6, name = (null)}
2022-05-10 11:32:45.020573+0800 多线程demo[10852:490868] 线程- 5 = <NSThread: 0x6000013f9d40>{number = 3, name = (null)}
2022-05-10 11:32:45.020857+0800 多线程demo[10852:490803] 迭代完成
要注意的是,和异步+并发不同,快速迭代需要执行完才能继续执行下一个.这也就是为什么"迭代完成"最后打印出来.
4 信号量
GCD 中的信号量是指Dispatch Semaphore,使用计数来完成这个功能.计数小于0时等待,不可通过.计数为0或者大于0是,不等待,可通过. 信号量的主要作用是:
-1 保持线程同步,将异步执行任务转化为同步执行任务
-2 保证线程安全,为线程加锁.
-(void)semaphore_gcdtest{
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//创建信号量 大于0 不会同步执行 =0 正常使用 会同步执行
dispatch_semaphore_t semapphore=dispatch_semaphore_create(0);
dispatch_async(globalQueue, ^{
sleep(2);
NSLog(@"当前的线程是:%@",[NSThread currentThread]);
//解锁 让信号量+1
dispatch_semaphore_signal(semapphore);
});
NSLog(@"开始枷锁");
//让信号量-1,当信号量<0时候, 会阻塞所在的线程.
dispatch_semaphore_wait(semapphore, DISPATCH_TIME_FOREVER);
NSLog(@"解锁执行完成");
}
结果为:2022-05-10 11:59:28.150768+0800 多线程demo[11166:506297] 开始枷锁
2022-05-10 11:59:30.152631+0800 多线程demo[11166:506502] 当前的线程是:<NSThread: 0x60000067c380>{number = 6, name = (null)}
2022-05-10 11:59:30.153033+0800 多线程demo[11166:506297] 解锁执行完成
总结:创建的是同步信号,信号量为0,创建了一个全局并发队列 异步执行,创建线程异步执行thread1,主线程打印 "开始枷锁",这个时候给线程枷锁,信号总量<0,阻塞了主线程.子线程还继续执行.
子线程然后解锁,信号量变为0,主线程继续执行.所以才打印出上述的结果.
下面再来一个例子,模拟抢票,来加深理解
-(void)ticket_gcdtest{
dispatch_queue_t golbaequeue=dispatch_get_global_queue(0, 0);
self.semaphore = dispatch_semaphore_create(1);
dispatch_async(golbaequeue, ^{
[self ticket_num];
});
dispatch_async(golbaequeue, ^{
[self ticket_num];
});
dispatch_async(golbaequeue, ^{
[self ticket_num];
});
dispatch_async(golbaequeue, ^{
[self ticket_num];
});
}
-(void)ticket_num{
while (1) {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketnum>0) {
self.ticketnum--;
NSLog(@"剩余票数 = %ld 线程:%@",self.ticketnum,[NSThread currentThread]);
sleep(1);
}else{
NSLog(@"售完");
dispatch_semaphore_signal(self.semaphore);
break;
}
dispatch_semaphore_signal(self.semaphore);
}
}
结果为:2022-05-10 14:47:36.466909+0800 多线程demo[12725:587462] 剩余票数 = 9 线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:37.469077+0800 多线程demo[12725:587463] 剩余票数 = 8 线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:38.474606+0800 多线程demo[12725:587469] 剩余票数 = 7 线程:<NSThread: 0x600002f4e440>{number = 8, name = (null)}
2022-05-10 14:47:39.478234+0800 多线程demo[12725:587464] 剩余票数 = 6 线程:<NSThread: 0x600002f3a780>{number = 9, name = (null)}
2022-05-10 14:47:40.483744+0800 多线程demo[12725:587462] 剩余票数 = 5 线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:41.489249+0800 多线程demo[12725:587463] 剩余票数 = 4 线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:42.494754+0800 多线程demo[12725:587469] 剩余票数 = 3 线程:<NSThread: 0x600002f4e440>{number = 8, name = (null)}
2022-05-10 14:47:43.500287+0800 多线程demo[12725:587464] 剩余票数 = 2 线程:<NSThread: 0x600002f3a780>{number = 9, name = (null)}
2022-05-10 14:47:44.505797+0800 多线程demo[12725:587462] 剩余票数 = 1 线程:<NSThread: 0x600002f3e080>{number = 6, name = (null)}
2022-05-10 14:47:45.511286+0800 多线程demo[12725:587463] 剩余票数 = 0 线程:<NSThread: 0x600002f6ca80>{number = 5, name = (null)}
2022-05-10 14:47:46.515650+0800 多线程demo[12725:587469] 售完
2022-05-10 14:47:46.516032+0800 多线程demo[12725:587464] 售完
2022-05-10 14:47:46.516473+0800 多线程demo[12725:587462] 售完
2022-05-10 14:47:46.516859+0800 多线程demo[12725:587463] 售完
为什么会出现这种结果呢?首先,这是并发+异步的组合,所以创建了新的子线程.再看ticket_num方法中的执行,是一个死循环,进入循环的时候先加锁,信号量-1,然后判断票数,当售完的时候 break返回.
我们接着看子线程,第一个进入的子线程实现ticket_num方法,这个时候经过加锁,信号量为0,变成了同步.其他子线程进入了方法,这个时候信号量为-1 -2 -3,阻塞了线程,等待.当出票完成后,解锁,信号量+1.这个时候执行之前信号量为-1的线程.如此循环执行.直到售完.
5 dispatch_once_t(单例)
创建单例
+ (instantClass *)sharedClient {
static instantClass *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[instantClass alloc] init];
});
return _sharedClient;
}
NSOperation
NSOperation和NSOperationQueue是对GCD的一层封装,NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类NSInvocationOperation和NSBlockOperation。
-(void)opertion_init{
NSInvocationOperation *opertion=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
[opertion start];
NSBlockOperation *blockopertion = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block A");
}];
[blockopertion addExecutionBlock:^{
NSLog(@"block B");
}];
[blockopertion addExecutionBlock:^{
NSLog(@"block C");
}];
[blockopertion start];
}
-(void)run{
NSLog(@"nsinvocation");
}
2、NSOperation之间可以设置依赖来保证执行顺序
一定要让操作A执行完后,才能执行操作B,
-(void)testdepence{
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
NSBlockOperation *block1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A");
}];
NSBlockOperation *block2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B");
}];
NSBlockOperation *block3=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"c");
}];
[block3 addDependency:block1];
[block1 addDependency:block2];
}
多线程的一些基础知识基本就这些了,后续会不断的完善,添加关于死锁 自旋锁 互斥锁的一些相关知识.