多线程

一:线程的概念

进程是指在系统中正在执行的应用程序,进程之前是相互独立的,而一个进程想要执行任务,就必须要有线程,进程中的任务都是先线程中执行,当进程中有多个任务的时候,系统可以开启多个线程,同时执行任务,这就是多线程

二:多线程方案种类
常见的多线程.jpeg
三:GCD

GCD实现原理:GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?)而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。 • 如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。 • 如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。 • 这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5-8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3-5条最为合理。

GCD中有2个用来执行任务的的函数,同步(dispatch_sync)和异步(dispatch_async),GCD的队列也分为两类,并发(多个任务同时进行,只在异步函数下才有效)和串行(任务一个接着一个的执行)


各种队列执行效果.jpeg

产生死锁的几种情况:

  1. 在主线程中开启同步主队列,在主线程中创建一个主队列,并开启同步函数即在主线程中执行任务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");
}
  1. 在主线程中开启异步主队列,在主线程中执行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");
}
  1. 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");
    });
五:线程阻塞

一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,当多个文件访问同一块资源的时候,很容易引发数据错乱和数据安全的问题,譬如存钱取钱,买票的例子
存钱取钱.jpeg
存钱取钱.jpeg

解决方案就是使用线程同步技术,就是协同不屌,按预定的先后次序进行,常见的线程同步技术是加锁,线程同步方案:OSSpinLock、os_unfair_lock、pthread_mutex、dispatch_semaphore、dispatch_queue、NSLock、NSRecursiveLock、NSCondition、NSConditionLock、@synchronize

1.OSSpinLock叫做“自旋锁”,等待锁的线程会处于忙等状态,一直占用CPU资源,目前已经不再安全,可能会出现优先级反转问题,如果等待锁的线程优先级较高,它会一直占用CPU资源,优先级低的线程就无法释放锁,需要导入头文件#import,需要保证是同一把锁
加锁代码

2.os_unfair_lock,用于取代不安全的OSSpinLock,iOS10开始才支持,从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
os_unfair_lock

3.pthread_mutex叫做互斥锁,等待的锁会处于休眠状态,当锁的属性设置为递归锁的时候,是允许同一个线程进行重复加锁的
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的封装

  1. NSConditionLock是对NSCondition的进一步封装
    image.png

    6.串行队列,dispatch_queue ,使用信号量dispatch_semaphore,信号量的初始值,可以用来控制线程并发访问的最大数量,当初始值为1的时候,表示同时允许一条线程访问资源, 保证线程同步
    dispatch_semaphore

    7.@synchronized ,是对mutex递归锁的封装

8.atomic用于保证属性getter、setter的原子性操作,相当于在getter、setter方法内部添加了线程同步锁,并不能保证属性在使用过程中是线程安全的,仅仅是方法内部,比如往可变数组添加数组这个操作需要同步锁,并不是getter、setter方法要同步,为什么不用,因为好性能,经常使用,nonatomic
9.iOS读写安全方案,IO操作,文件操作
同一时间,只能有一个线程进行写的操作、同一时间允许多个线程进行读的操作、不允许同一时间进行读和写的操作,简单就是多读单写,实现方案pthread_rwlock,dispatch_barrier_async

pthread_rwlock,等待会进入休眠,代码实现
代码实现
dispatch_barrier_async,这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
代码实现

你可能感兴趣的:(多线程)