IOS底层原理 -7.多线程

练习代码地址:

1. iOS中多线程方案

最底层API pthread: 是一套通用的跨平台的多线程API,是基于c语言,线程的生命周期需要手动管理;
NSThread:是对pthread用OC语言的封装,使得操作线程可以像操作对象一样操作;
使用最多的GCD:替代NSThread的多线程API,可以充分利用多核技术;执行的子线程任务都在块里(block);其生命周期自行维护;
NSOperation:是基于GCD的面向对象的封装,比起gcd使用更加简单,同时也增加了一些实用功能;

2. GCD 同步,异步,串行,并行

2.1 从一到面试题入手认识下GCD:

下面代码会发生什么?

    NSLog(@"步骤1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"步骤2");
    });
    NSLog(@"步骤3");

答案是:输出步骤1 后程序会崩溃;看一下实际运行后的截图:
在这里插入图片描述
dispatch_get_main_queue()获取的是主队列,主队列等同于串行队列;先来看下iOS中多线程的四个名词:

2.2 多线程易混淆的名词

同步sync,异步async,并行队列Concurrent Dispatch Queue,串行队列Serial Dispatch Queue;

  • 并行队列Concurrent Dispatch Queue:可以让多个任务同时并发执行,并同时开启新的线程,并发队列只有在异步任务下才有效;
  • 串行队列Serial Dispatch Queue:一个任务执行完成后才能执行下一个任务;
并发队列 串行队列 主队列
sync 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
async 有开启新线程,并发执行任务 有开启新线程,串行执行任务 没有开启新线程,串行执行任务

通过代码对上表进行验证下:

    dispatch_queue_t serialQue  = dispatch_queue_create("com.lym.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrentQue  = dispatch_queue_create("com.lym.concurrent", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"步骤0");
    dispatch_async(serialQue, ^{
        NSLog(@"串行队列 异步任务 1");
    });
    dispatch_async(serialQue, ^{
        NSLog(@"串行队列 异步任务 2");
    });
    dispatch_async(serialQue, ^{
        NSLog(@"串行队列 异步任务 3");
    });
    NSLog(@"------------------------");
    dispatch_async(concurrentQue, ^{
        NSLog(@"并发队列 异步任务 1");
    });
    dispatch_async(concurrentQue, ^{
        NSLog(@"并发队列 异步任务 2");
    });
    dispatch_async(concurrentQue, ^{
        NSLog(@"并发队列 异步任务 3");
    });
    NSLog(@"------------------------");
    dispatch_sync(concurrentQue, ^{
        NSLog(@"并发队列 同步任务 1");
    });
    dispatch_sync(concurrentQue, ^{
        NSLog(@"并发队列 同步任务 2");
    });
    dispatch_sync(concurrentQue, ^{
        NSLog(@"并发队列 同步任务 3");
    });
    NSLog(@"------------------------");
    dispatch_sync(serialQue, ^{
        NSLog(@"串行队列 同步任务 1");
    });
    dispatch_sync(serialQue, ^{
        NSLog(@"串行队列 同步任务 2");
    });
    dispatch_sync(serialQue, ^{
        NSLog(@"串行队列 同步任务 3");
    });
    NSLog(@"步骤2");

输出结果:
在这里插入图片描述

2.3 面试题解析

下面再来分析下锁住的原因:iOS中函数默认都是运行在主线程中,而主线程所在的主队列是串行队列;那么上述函数中在主队列中有一个任务(viewDidLoad)开始执行,当执行到dispatch_sync的时候串行队列中有多了个同步任务,那么上一个任务(viewDidLoad)等待该任务执行完才能接着执行,但是因为是同步线程,dispatch_sync的任务需要等到上一个任务执行完成才能执行;这样就造成两个任务相互等待对方执行完成,从而造成死锁;解决方式有以下两种:
a,主队列中异步执行:

    NSLog(@"步骤1");
    dispatch_queue_t mianQue  = dispatch_get_main_queue();
    dispatch_async(mianQue, ^{
        NSLog(@"步骤2");
    });

    NSLog(@"步骤3");
    //2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步骤1
    //2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步骤2
    //2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步骤3

b, 新队列中执行:

    NSLog(@"步骤1");
    dispatch_queue_t que  = dispatch_queue_create("kkk", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(que, ^{//异步这里就不再测试
        NSLog(@"步骤2");
    });
    NSLog(@"步骤3");
    
   //2019-09-28 10:16:07.384373+0800 testThread[20853:156963] 步骤1
   //2019-09-28 10:16:07.384633+0800 testThread[20853:156963] 步骤2
   //2019-09-28 10:16:07.384782+0800 testThread[20853:156963] 步骤3

**

注意:使用sync在当前串行队列中添加任务,会是当前队列产生死锁;

**

3 多人线程与runloop

3.1 面试题1

     NSLog(@"步骤1");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"步骤2");
    });
    NSLog(@"步骤3");
- (void)test{
    NSLog(@"%s",__func__);
}
  • 执行结果是:
2019-09-28 10:56:17.360642+0800 testThread[27068:210230] 步骤1
2019-09-28 10:56:17.360920+0800 testThread[27068:210230] 步骤3
2019-09-28 10:56:17.361117+0800 testThread[27068:210325] 步骤2

从上面打印可以看出并没有执行test()方法;

  • 分析:
    如果没有afterDelay:那么该方法可以在objc源码里找到实现,其底层最终调用了msgSend;而有afterDelay:后在Runloop.h中,其底层实现没有开源,但是可以通过相似的开源项目GNUSetup,知道其底层是添加了一个定时器;而子线程中的定时器是依靠runloop,如果没有获取过,子线程是没有runloop;所以不会去执行test方法。
  • 解决方法
     NSLog(@"步骤1");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"步骤2");
    });
    NSLog(@"步骤3");
- (void)test{
    NSLog(@"%s",__func__);
    CFRunLoopStop(CFRunLoopGetCurrent());
}
2019-09-28 11:03:08.085945+0800 testThread[28275:221117] 步骤1
2019-09-28 11:03:08.086135+0800 testThread[28275:221117] 步骤3
2019-09-28 11:03:08.086765+0800 testThread[28275:221198] -[ViewController test]
2019-09-28 11:03:08.087350+0800 testThread[28275:221198] 步骤2

3.2 面试题2

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"步骤0");
    NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
        NSLog(@"步骤1");
    }];
    [curentThres start];
    NSLog(@"步骤2");
    [self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}

运行结果如下:

2019-09-28 11:16:04.951807+0800 testThread[30020:237237] 步骤0
2019-09-28 11:16:04.952187+0800 testThread[30020:237237] 步骤2
2019-09-28 11:16:04.954974+0800 testThread[30020:237393] 步骤1
2019-09-28 11:16:04.980856+0800 testThread[30020:237237] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'

原因是在执行performSelector的时候curentThres在执行完一个任务后已经销毁了,解决方法也是runloop:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"步骤0");
    NSThread *curentThres = [[NSThread alloc]initWithBlock:^{
        NSLog(@"步骤1");
        //不添加下面这一句会崩溃
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [curentThres start];
    NSLog(@"步骤2");
    [self performSelector:@selector(test) onThread:curentThres withObject:nil waitUntilDone:YES];
}

4 队列组

多个并发的任务执行完成后再执行另一个任务,最经典的就是多线程下载的应用;使用很简单;我们直接看源码实例:

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t concurrentQue  = dispatch_queue_create("com.lym.concurrentGroup", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"步骤0");
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"并发队列组 异步任务 1");
       
    });
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"并发队列组 异步任务 2");
    });
    dispatch_group_async(group,concurrentQue, ^{
         [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"并发队列组 异步任务 3");
    });
    dispatch_group_notify(group, concurrentQue, ^{
        NSLog(@"全部执行完成");
    });
    NSLog(@"步骤2");
2019-09-28 11:31:12.132972+0800 testThread[32679:259275] 步骤0
2019-09-28 11:31:12.133151+0800 testThread[32679:259275] 步骤2
2019-09-28 11:31:14.135128+0800 testThread[32679:259330] 并发队列组 异步任务 2
2019-09-28 11:31:14.135159+0800 testThread[32679:259331] 并发队列组 异步任务 3
2019-09-28 11:31:14.135147+0800 testThread[32679:259329] 并发队列组 异步任务 1
2019-09-28 11:31:14.135595+0800 testThread[32679:259331] 全部执行完成

5 线程安全问题

5.1 异常例子

#import "LYMBaseThreadTast.h"

@interface LYMBaseThreadTast ()
@property(nonatomic,assign)NSInteger cont;

@end

@implementation LYMBaseThreadTast
/*****售票问题******/
-(void)scaleTicket{
    int count = (int)_cont;
    count = count - 1;;
    sleep(.3);
    self.cont = count;
    NSLog(@"还剩%ld currenthread:%@",_cont,[NSThread currentThread]);
    
}
-(void)scaleTicketsTask{
    self.cont = 20;
    dispatch_queue_t scaleTicketQue  = dispatch_queue_create("com.lym.scaleTicket", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(scaleTicketQue, ^{
        for (int i = 10; i > 0; i--) {
            [self scaleTicket];
        }
        
    });
    dispatch_async(scaleTicketQue, ^{
        for (int i = 5; i > 0; i--) {
            [self scaleTicket];
        }
    });
    dispatch_async(scaleTicketQue, ^{
        for (int i = 5; i > 0; i--) {
            [self scaleTicket];
        }
    });
}
/*****end****/
@end

输出:

2019-09-28 11:58:44.783825+0800 testThread[36523:296048] 还剩19 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.783831+0800 testThread[36523:296045] 还剩19 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.783932+0800 testThread[36523:296046] 还剩19 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296046] 还剩18 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784113+0800 testThread[36523:296048] 还剩18 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784116+0800 testThread[36523:296045] 还剩18 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784242+0800 testThread[36523:296046] 还剩17 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.784246+0800 testThread[36523:296048] 还剩17 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.784254+0800 testThread[36523:296045] 还剩16 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.784345+0800 testThread[36523:296046] 还剩15 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785462+0800 testThread[36523:296048] 还剩14 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.785538+0800 testThread[36523:296045] 还剩13 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.785745+0800 testThread[36523:296046] 还剩12 currenthread:<NSThread: 0x6000006691c0>{number = 4, name = (null)}
2019-09-28 11:58:44.785979+0800 testThread[36523:296048] 还剩11 currenthread:<NSThread: 0x60000065e400>{number = 3, name = (null)}
2019-09-28 11:58:44.786492+0800 testThread[36523:296045] 还剩10 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.786959+0800 testThread[36523:296045] 还剩9 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.787282+0800 testThread[36523:296045] 还剩8 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791496+0800 testThread[36523:296045] 还剩7 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791644+0800 testThread[36523:296045] 还剩6 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}
2019-09-28 11:58:44.791770+0800 testThread[36523:296045] 还剩5 currenthread:<NSThread: 0x60000066cc80>{number = 5, name = (null)}

从上面的例子里可以看到,当多个线程同时读写一个变量的时候就会出现异常

5.2 异常例子 解决方法:

5.2.1 自旋锁 OSSpinLock

将上述方法修改如下:

 #import "LYMOSSpinLockThreadTast.h"
#import <libkern/OSAtomic.h>


@interface LYMOSSpinLockThreadTast ()
@property(nonatomic,assign)OSSpinLock lock;
@end
@implementation LYMOSSpinLockThreadTast
-(instancetype)init{
    if (self=[super init]) {
        self.lock =  OS_SPINLOCK_INIT;
    }
    return self;
}
/*****售票问题 自旋锁解决方式******/
-(void)scaleTicket{
    OSSpinLockLock(&_lock);//自旋锁加锁
    [super scaleTicket];
    OSSpinLockUnlock(&_lock);//自旋锁解锁
}
/*****end****/
@end
/*****end****/

输出:

2019-12-01 13:00:45.190729+0800  还剩19 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191271+0800  还剩18 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.191511+0800 还剩17 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193041+0800 还剩16 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193231+0800 还剩15 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193416+0800 还剩14 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193586+0800 还剩13 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.193772+0800  还剩12 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.194042+0800  还剩11 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.198458+0800 还剩10 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a115c0>{number = 3, name = (null)}
2019-12-01 13:00:45.199288+0800  还剩9 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199431+0800  还剩8 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199560+0800 还剩7 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199700+0800 还剩6 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.199816+0800  还剩5 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a2af40>{number = 4, name = (null)}
2019-12-01 13:00:45.203833+0800  还剩4 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204001+0800  还剩3 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204128+0800 还剩2 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204258+0800  还剩1 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}
2019-12-01 13:00:45.204381+0800  还剩0 scaleTicket_OSSpinLock currenthread:<NSThread: 0x600000a10080>{number = 5, name = (null)}

自旋锁实现大致是一个循环不断去判断是不是完成任务,就是说线程会一直占用cpu资源即:忙等;假如这个等待的线程是级别比较高的线程,那么线程调度时候就会导致其他线程无法执行;及线程等级反转。
所以苹果已经不推荐使用自旋锁;因此推荐使用os_unfair_lock:

#import "LYMOSUnfairLockThreadTast.h"
#import <os/lock.h>

@interface LYMOSUnfairLockThreadTast ()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation LYMOSUnfairLockThreadTast
/*****售票问题 os_unfair_lock  #import <os/lock.h>******/
-(instancetype)init{
    if (self=[super init]) {
        self.ticketLock =  OS_UNFAIR_LOCK_INIT;
    }
    return self;
}
-(void)scaleTicket{
    os_unfair_lock_lock(&_ticketLock);
    [super scaleTicket];
    os_unfair_lock_unlock(&_ticketLock);//解锁
    
}
/*****end****/
@end

5.2.2 pthread_mutex_t 互斥锁

#import "LYMOSPThreadTast.h"
#import <pthread.h>

@interface LYMOSPThreadTast ()
@property(nonatomic,assign)pthread_mutex_t mutex_lock;//互斥锁
@end
@implementation LYMOSPThreadTast
/*****售票问题 os_unfair_lock  #import <os/lock.h>******/
-(instancetype)init{
    if (self=[super init]) {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);//普通锁
        pthread_mutex_init(&(_mutex_lock), &attr);//第二个参数默认可以为NULL
    }
    return self;
}
-(void)scaleTicket{
    pthread_mutex_lock(&(_mutex_lock));
    [super scaleTicket];
    pthread_mutex_unlock(&(_mutex_lock));//解锁
    
}
-(void)dealloc{
    pthread_mutex_destroy(&(_mutex_lock));
}
/*****end****/
@end

上述代码中加锁的代码中如果有调用了使用同一把锁加锁的代码,就会造成死锁,这时候应该使用递归锁pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//递归锁

在加锁与解锁成对的线程中,递归锁允许对同一把锁重复加锁

5.2.2 NSCondition(条件锁),NSConditionLock(条件锁的封装)

//
//  LYMNSCordition.m
//  testThread
//
//  Created by ymluo on 2019/12/6.
//  Copyright © 2019 ymluo. All rights reserved.
//

#import "LYMNSCordition.h"

@interface LYMNSCordition()
@property (strong, nonatomic)NSCondition  *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation LYMNSCordition

- (instancetype)init
{
    if (self = [super init]) {
        //当内部的d条件值等于1的时候,才能加锁
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    //    [[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    
}

- (void)__two
{
     [_condition lock];
    NSLog(@"__fnc:%s 2",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"__fnc:%s 2  sleep",__func__);
        sleep(2);
        [self.condition signal];
        NSLog(@"__fnc:%s 2  sleep untile",__func__);
    });
    [_condition wait];
    [_condition unlock];
    NSLog(@"__fnc:%s 2 end function",__func__);
}

- (void)__one
{
    [_condition lock];
    NSLog(@"__fnc:%s 1",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"__fnc:%s 1  sleep",__func__);
        sleep(2);
        [self.condition signal];
        NSLog(@"__fnc:%s 1  sleep untile",__func__);
    });
    [_condition wait];
    [_condition unlock];
    [self __two];
}
@end

// 输出

2019-12-06 09:01:21.794134+0800  __fnc:-[LYMNSCordition __one] 1
2019-12-06 09:01:21.806155+0800  __fnc:-[LYMNSCordition __one]_block_invoke 1  sleep
2019-12-06 09:01:23.806536+0800  __fnc:-[LYMNSCordition __one]_block_invoke 1  sleep untile
2019-12-06 09:01:23.806533+0800  __fnc:-[LYMNSCordition __two] 2
2019-12-06 09:01:23.808100+0800  __fnc:-[LYMNSCordition __two]_block_invoke 2  sleep
2019-12-06 09:01:25.809708+0800  __fnc:-[LYMNSCordition __two]_block_invoke 2  sleep untile
2019-12-06 09:01:25.809708+0800  __fnc:-[LYMNSCordition __two] 2 end function

//
//  LYMNsconditionLock.m
//  testThread
//
//  Created by ymluo on 2019/12/6.
//  Copyright © 2019 ymluo. All rights reserved.
//

#import "LYMNsconditionLock.h"
@interface LYMNsconditionLock()
@property (strong, nonatomic)NSConditionLock  *conditionLock;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation LYMNsconditionLock

- (instancetype)init
{
    if (self = [super init]) {
        //当内部的d条件值等于1的时候,才能加锁
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__third) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(_one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
  
}


/**
 因为加锁的内部条件是condition等于1 所以该线程的方法会等待线程2的__two执行完毕
 */
- (void)_one
{
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__fnc:%s 2",__func__);
    
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}
/**
 因为加锁的内部条件是condition等于1 所以该线程的方法会最先执行
 */
- (void)__two
{
    [self.conditionLock lockWhenCondition:1];
    NSLog(@"__fnc:%s 1",__func__);
    dispatch_async(dispatch_get_main_queue(), ^{
            
    });
     sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}
/**
 因为加锁的内部条件是condition等于1 所以该线程的方法会等到 1和2都执行完毕且condition的值等于开始执行下一个 方法
 */
- (void)__third{
    [self.conditionLock lockWhenCondition:3];
   NSLog(@"__fnc:%s 3",__func__);
    
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
    
}
@end



2019-12-06 09:07:16.887674+0800  __fnc:-[LYMNsconditionLock __two] 1
2019-12-06 09:07:17.893224+0800  __fnc:-[LYMNsconditionLock _one] 2
2019-12-06 09:07:18.898025+0800  __fnc:-[LYMNsconditionLock __third] 3

5.3 dispatch_semaphore 信号量

信号量可以控制同时最大并发数,也可当做线程锁使用;

  • 1: 线程数控制:
#pragma mark - 控制同时执行线程数(控制最大并发数)
- (void)otherTestMaxThred{
    for (NSInteger i = 30; i > 0; i--) {
        NSThread *thread = [[NSThread currentThread]initWithTarget:self selector:@selector(__thestThred:) object:@(i)];
        [thread start];
    }
}
- (void)__thestThred:(id)obj{
//    如果信号量的值 > 0,那么就让信号量的值减一,且继续向下执行;
//    如果 <= o,则等待(线程休眠等待),直到dispatch_semaphore_signal()让信号量的值 +1,就让信号量的值 -1,然后继续执行代码。
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    sleep(2);
    NSLog(@"thread:%@ i:%@",[NSThread currentThread],obj);
//    让信号量的值 +1;
    dispatch_semaphore_signal(_dispatchSemapthore);
}
  • 2 同步锁
#import "LYMSemphoreTest.h"


@interface LYMSemphoreTest ()

@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore;  //!<信号量

@end
@implementation LYMSemphoreTest
-(instancetype)init{
    if (self = [super init]) {
        self.dispatchSemapthore = dispatch_semaphore_create(1);
    }
    return self;
}
-(void)scaleTicket{
    //    如果信号量的值 > 0,那么就让信号量的值减一,且继续向下执行;
    //    如果 <= o,则等待(线程休眠等待),直到dispatch_semaphore_signal()让信号量的值 +1,就让信号量的值 -1,然后继续执行代码。
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    [super scaleTicket];
    //    让信号量的值 +1;
    dispatch_semaphore_signal(_dispatchSemapthore);
    
}
@end

2019-12-08 11:43:20.307289+0800 还剩19 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.307705+0800 还剩18 currenthread:<NSThread: 0x60000202cec0>{number = 4, name = (null)}
。。。。。
2019-12-08 11:43:20.320493+0800 还剩1 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
2019-12-08 11:43:20.320789+0800 还剩0 currenthread:<NSThread: 0x60000201cd40>{number = 3, name = (null)}
  • 3 子线程多任务同步
/**
 线程中等待子线程执行返回后继续执行任务
 */
- (void)otherTestThred{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
         [self __thestThred1:@(0)];
    });
   
}
- (void)__thestThred1:(id)obj{
     NSLog(@"===begin thread:%@ i:%@",[NSThread currentThread],obj);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"thread 1:%@ i:%@",[NSThread currentThread],obj);
        sleep(2);
        NSLog(@"thread 2:%@ i:%@",[NSThread currentThread],obj);
        //    让信号量的值 +1;
        dispatch_semaphore_signal(self.dispatchSemapthore);
    });
    dispatch_semaphore_wait(_dispatchSemapthore, DISPATCH_TIME_FOREVER);
    NSLog(@"===end thread:%@ i:%@",[NSThread currentThread],obj);

输出:

2019-12-08 11:48:44.393387+0800 ===begin thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0
2019-12-08 11:48:44.393922+0800  thread 1<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396430+0800 thread 2<NSThread: 0x6000021d5c00>{number = 4, name = (null)} i:0
2019-12-08 11:48:46.396631+0800 ===end thread:<NSThread: 0x6000021eee40>{number = 3, name = (null)} i:0

5.4 @synchronized

是对mutex递归锁的封装,参数self,在底层中将其地址作为key去取出对应的锁;

 @synchronized (self) {
        // Task
    }

6 锁性能问题

性能由高到低依次如下:
os_unfair_lock:性能最高,
OSSpinLock:预计线程等待时间很短的时候;cpu资源足够;很少发生竞争;
dispatch_semaphore:
pthread_mutex:
dispatch_queue(DISPATCH_QUEUE_SERIAL);//同步队列
NSLock
NSCondition
pthread_mutex():recursive//递归锁
NSRecuriseiveLock
NSConditionLock
@synchronized

7 IO 操作安全问题

多读单写:pthread_rwlock 等待的锁会进入 休眠

@interface LYMIOSafeTest ()
//@property (nonatomic, strong) dispatch_semaphore_t dispatchSemapthore;  //!<信号量
//@property (nonatomic, copy) NSString *filePath;  //!<<#注释#>
@property (nonatomic, assign) pthread_rwlock_t rwLock;  //!<信号量
@property (nonatomic, copy) NSString *filePath;  //!<<#注释#>

@end
@implementation LYMIOSafeTest

-(instancetype)init{
    if (self = [super init]) {
        pthread_rwlock_init(&(_rwLock), NULL);
        NSString *documetPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        self.filePath = [documetPath stringByAppendingPathComponent:@"testFile.txt"];
    }
    return self;
}

-(void)otherTest{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(queue, ^{
            [self __writeileThred:@(i)];
        });
        dispatch_async(queue, ^{
            [self __readFileThred:@(i)];
        });
    }
}
- (void)__readFileThred:(id)obj{
    pthread_rwlock_rdlock(&_rwLock);
    if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
        NSString *resultStr = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"__readFileThred=====:str= %@",resultStr);
    }
    sleep(1);
    pthread_rwlock_unlock(&_rwLock);
}
- (void)__writeileThred:(id)obj{
   pthread_rwlock_wrlock(&_rwLock);
    // 字符串读取的方法
    if ([obj integerValue] == 0) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
            [[NSFileManager defaultManager] removeItemAtPath:_filePath error:nil];
        }
    }
    if (![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
        [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];
    }
    NSFileHandle *handler = [NSFileHandle fileHandleForUpdatingAtPath:_filePath];
    [handler seekToEndOfFile];
    [handler writeData:[[NSString stringWithFormat:@"\n线程开始写入数据:%@",obj] dataUsingEncoding:NSUTF8StringEncoding]];
    NSLog(@"__writeileThred thread:%@",[NSThread currentThread]);
    sleep(1);
    pthread_rwlock_unlock(&_rwLock);
}
-(void)dealloc{
    pthread_rwlock_destroy(&_rwLock);
}

输出:

2019-12-08 14:33:15.376561+0800  __writeileThred thread:<NSThread: 0x600002b4ddc0>{number = 3, name = (null)}
2019-12-08 14:33:16.382122+0800  __readFileThred=====:str= 
线程开始写入数据:0
2019-12-08 14:33:16.382244+0800  __readFileThred=====:str= 
线程开始写入数据:0
2019-12-08 14:33:17.385887+0800  __writeileThred thread:<NSThread: 0x600002b7d640>{number = 4, name = (null)}
2019-12-08 14:33:18.392534+0800  __writeileThred thread:<NSThread: 0x600002b4dfc0>{number = 5, name = (null)}
2019-12-08 14:33:19.395925+0800  __readFileThred=====:str= 
线程开始写入数据:0
线程开始写入数据:1
线程开始写入数据:2
2019-12-08 14:33:20.401841+0800  __writeileThred thread:<NSThread: 0x600002b4df40>{number = 6, name = (null)}
2019-12-08 14:33:21.409091+0800  __readFileThred=====:str= 
线程开始写入数据:0
线程开始写入数据:1
线程开始写入数据:2
线程开始写入数据:3
2019-12-08 14:33:22.413197+0800  __writeileThred thread:<NSThread: 0x600002b7c640>{number = 7, name = (null)}
2019-12-08 14:33:23.417572+0800  __readFileThred=====:str= 
线程开始写入数据:0
线程开始写入数据:1
线程开始写入数据:2
线程开始写入数据:3
线程开始写入数据:4

从输出中可以看出读可以多个同时执行,而写同时只能存在一个;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章