一:线程的概念
进程是指在系统中正在执行的应用程序,进程之前是相互独立的,而一个进程想要执行任务,就必须要有线程,进程中的任务都是先线程中执行,当进程中有多个任务的时候,系统可以开启多个线程,同时执行任务,这就是多线程
二:多线程方案种类
三:GCD
GCD实现原理:GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?)而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。 • 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。 • 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。 • 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5-8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3-5条最为合理。
GCD中有2个用来执行任务的的函数,同步(dispatch_sync)和异步(dispatch_async),GCD的队列也分为两类,并发(多个任务同时进行,只在异步函数下才有效)和串行(任务一个接着一个的执行)
产生死锁的几种情况:
- 在主线程中开启同步主队列,在主线程中创建一个主队列,并开启同步函数即在主线程中执行任务2,由于interlock1函数和任务2都在主队列中,在执行任务2的时候,需要先执行完任务3即interlock1函数都执行完才能执行任务2,但是执行完函数interlock1又必须先执行任务2,这样就导致函数interlock1执行完先要执行完任务2,任务2执行完又要先执行完函数interlock1,你等我我等你导致互锁
- (void)interlock1
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
// dispatch_sync立马在当前线程同步执行任务
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
- 在主线程中开启异步主队列,在主线程中执行interlock2函数,执行到dispatch_async任务的时候,虽然任务2也是在主队列中,但由于是异步的,不会要求立马执行任务2,可以先执行任务3后再执行任务2,所以不会产生死锁
- (void)interlock2
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
// dispatch_async不要求立马在当前线程同步执行任务
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
- dispatch_async异步开启了一个串行队列,执行完任务2到dispatch_sync时,这个时候又开启了一个同步函数,并把他放到同一个串行队列中,同1一样,由于是任务都在同一个队列中,又是串行,执行任务3必须要先执行完任务4,即dispatch_async的任务都要执行完,但是dispatch_async的任务都要执行完,又先要执行完任务3,这就导致了死锁
- (void)interlock3
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
4.dispatch_async异步开启了一个串行队列,执行完任务2到dispatch_sync时,这个时候又开启了一个同步函数,并把他放到另一个串行队列中,由于是另一个队列,所以不必等待,打印顺序是任务1、5、2、3、4
- (void)interlock4
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
5.并发队列,任务可以同时就行,不需要等待,不存在死锁,打印顺序任务1、5、2、3、4
- (void)interlock5
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
总结:使用dispatch_sync同步函数往当前串行队列中添加任务,会卡住当前的串行队列,导致死锁问题,注意“当前”
四:队列组的使用
异步并发执行任务1、2,等任务1、2执行完之后再回到主线程执行任务3
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t quene = dispatch_queue_create("heihei", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, quene, ^{
NSLog(@"1111");
});
dispatch_group_async(group, quene, ^{
NSLog(@"2222");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"3333");
});
五:线程阻塞
一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,当多个文件访问同一块资源的时候,很容易引发数据错乱和数据安全的问题,譬如存钱取钱,买票的例子解决方案就是使用线程同步技术,就是协同不屌,按预定的先后次序进行,常见的线程同步技术是加锁,线程同步方案:OSSpinLock、os_unfair_lock、pthread_mutex、dispatch_semaphore、dispatch_queue、NSLock、NSRecursiveLock、NSCondition、NSConditionLock、@synchronize
2.os_unfair_lock,用于取代不安全的OSSpinLock,iOS10开始才支持,从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
3.pthread_mutex叫做互斥锁,等待的锁会处于休眠状态,当锁的属性设置为递归锁的时候,是允许同一个线程进行重复加锁的
pthread_mutex的条件,两个线程,一个执行对可变数组的删除,一个执行添加,当先执行删除操作时,条件是数组中有元素的时候才删除,
- (void)__remove
{
pthread_mutex_lock(&_mutex);
if (self.data.count == 0) {
// 等待的锁会进行休眠,并将这把锁放开
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
}
// 线程2 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号
pthread_cond_signal(&_cond);唤醒这把锁,重新加锁
pthread_mutex_unlock(&_mutex);
}
自旋锁和互斥锁的区别是自旋锁处于忙等状态,不会休眠,二互斥锁是不会处于忙等状态的,会处于休眠状态
4.NSLock、NSRecursiveLock、NSCondition
NSLock是对mutex普通锁的封装、NSRecursiveLock也是对mutex递归所的封装,NSCondition是对mutex和cond的封装
- NSConditionLock是对NSCondition的进一步封装
6.串行队列,dispatch_queue ,使用信号量dispatch_semaphore,信号量的初始值,可以用来控制线程并发访问的最大数量,当初始值为1的时候,表示同时允许一条线程访问资源, 保证线程同步
7.@synchronized ,是对mutex递归锁的封装
8.atomic用于保证属性getter、setter的原子性操作,相当于在getter、setter方法内部添加了线程同步锁,并不能保证属性在使用过程中是线程安全的,仅仅是方法内部,比如往可变数组添加数组这个操作需要同步锁,并不是getter、setter方法要同步,为什么不用,因为好性能,经常使用,nonatomic
9.iOS读写安全方案,IO操作,文件操作
同一时间,只能有一个线程进行写的操作、同一时间允许多个线程进行读的操作、不允许同一时间进行读和写的操作,简单就是多读单写,实现方案pthread_rwlock,dispatch_barrier_async