多线程编程小结

简单回顾

image.png

NSThread
NSThread的常见用法
- (void)创建及使用 {
    // 创建
    [NSThread detachNewThreadWithBlock:^{
        [self doSomething];
    }];
    [NSThread detachNewThreadSelector:@selector(doSomething) toTarget:self withObject:nil];
    NSThread *threadOne = [[NSThread alloc] initWithBlock:^{
        [self doSomething];
    }];
    NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething) object:nil];
    
    // name
    [threadTwo setName:@"threadTwo"];
    // 程使用栈区大小,默认是512K
    [threadTwo setStackSize:512];
    // iOS8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置
    [threadTwo setQualityOfService:NSQualityOfServiceUserInteractive];
    // 线程优先级设置 iOS8以前使用 给当前线程设定优先级,调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。
    [threadTwo setThreadPriority:[NSThread mainThread].threadPriority]; //线程优先级设置 iOS8以前使用
    // 状态
    [threadTwo isExecuting];
    [threadTwo isCancelled];
    [threadTwo isFinished];
    [threadTwo isMainThread];
    
    //开启
    [threadTwo start];
    //取消线程并不会马上停止并退出线程,仅仅只作(线程是否需要退出)状态记录
    [threadOne cancel];
    
    // 当前线程是否子线程
    [NSThread isMultiThreaded];
    // 获取当前线程、主线程
    [NSThread currentThread];
    [NSThread mainThread];
    
    [NSThread exit];//停止方法会立即终止除主线程以外所有线程(无论是否在执行任务)并退出,需要在掌控所有线程状态的情况下调用此方法,否则可能会导致内存问题。
    // sleep
    [NSThread sleepForTimeInterval:1.0];
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    //  给当前线程设定优先级,调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。
    [NSThread setThreadPriority:1.0];
    
    // 线程的调用都会有函数的调用函数的调用就会有栈返回地址的记录,在这里返回的是函 数调用返回的虚拟地址,说白了就是在该线程中函数调用的虚拟地址的数组
    NSArray *addressArray = [NSThread callStackReturnAddresses];
    // 同上面的方法一样,只不过返回的是该线程调用函数的名字数字
    NSArray *nameArray = [NSThread callStackSymbols];
}

线程切换

- (void)切换线程 {
    
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(延时操作) object:nil];
    [thread setName:@"切换线程的thread"];
    [thread start];
}
- (void)延时操作 {
    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    [NSThread sleepForTimeInterval:2.0f];
    [self performSelectorOnMainThread:@selector(主线程更新视图) withObject:nil waitUntilDone:YES];
}
- (void)主线程更新视图 {

    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
}
  • NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
  • NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
  • NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
  • NSQualityOfServiceUtility:普通优先级,用于普通任务
  • NSQualityOfServiceBackground:最低优先级,用于不重要的任务

NSOperation

常见用法
- (void)createInvocationOperation {
    
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething) object:nil];
    operation.completionBlock = ^{
        
        NSLog(@"completionBlock --%s-- IN thread:%@",__func__,[NSThread currentThread]);
    };
    [operation start];
    
}
#pragma mark -
- (void)切换线程 {
    
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
                                                                           selector:@selector(延时操作)
                                                                             object:nil];
       
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue setName:@"OperationQueue"];
    [queue setQualityOfService:NSQualityOfServiceUserInteractive];
    [queue setMaxConcurrentOperationCount:5];
    [queue addOperation:operation];
}
- (void)延时操作 {
    
    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    [NSThread sleepForTimeInterval:2.0f];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
                                                                           selector:@selector(主线程更新视图)
                                                                             object:nil];
    [[NSOperationQueue mainQueue] addOperation:operation];
    
}
- (void)主线程更新视图 {

    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
}


#pragma mark - NSBlockOperation
- (void)createBlockOperation {
    // 追加操作并发执行
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^() {
        NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^() {
        NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^() {
        NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
    }];
    
    // 开始执行任务
    [operation start];
    
}


- (void)doSomething {
    
    NSLog(@"doSomething IN thread:%@",[NSThread currentThread]);
}
添加任务依赖
- (void)创建依赖 {
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    //创建最后一个操作
    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:5.f];
        NSLog(@"开始的任务");
    }];
    for (int i=0; i<5; ++i) {
        //创建多线程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:i];
            NSLog(@"第%d个任务",i);
        }];
        //设置依赖操作为最后一个操作
        [blockOperation addDependency:lastBlockOperation];
        [operationQueue addOperation:blockOperation];
        
    }
    //将最后一个操作加入线程队列
    [operationQueue addOperation:lastBlockOperation];
// log    
// 开始的任务
// 第0个任务
// 第1个任务
// 第2个任务
// 第3个任务
// 第4个任务
}
任务执行状态控制(kvc)
isReady
isExecuting
isFinished
isCancelled
如果只重写了main,底层控制变更任务执行完成状态,以及任务退出
如果重写了start,自行控制任务状态
最大并发量 MaxConcurrentOperationCount

队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。

自定义 NSOperation
自定义非并发NSOperation

Methods to Override
For non-concurrent operations, you typically override only one method: main
Into this method, you place the code needed to perform the given task.

在官方文档中指出,非并发任务,直接将需要执行的任务放在main方法中,然后直接调用即可。 这样直接调用main方法会存在一个问题,由于没有实现finished属性,所以获取finished属性时,只会返回NO,而且任务加入到队列后,不会被删除,另外任务执行完后,回调也不会被执行,所以最好不要只实现一个main方法来使用。

- (void)main {
    // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
    @autoreleasepool {
        if (self.isCancelled) {
            return;
        }
        
        // 耗时操作
        
        if (self.isCancelled) {
            return;
        }
        if (self.responseBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.responseBlock();
            });
        }
    }
}
自定义并发NSOperation

Methods to Override
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
start
asynchronous
executing
finished

  • 重写NSOperation的main方法,该方法会作为NSOperation所启动线程的执行体
    在 operation 的 main 方法里面,必须提供 autorelease pool,因为你的 operation 完成后需要销毁
  • 实现KVO机制,一旦你的 operation 开始了,必须通过 KVO,告诉所有的监听者,现在该operation的执行状态。(即修改isFinished,isExecuting的值)
  • 重载 isExecuting方法和isFinished方法。在这两个方法中,必须返回一个线程安全值(通常是个BOOL值),这个值可以在 operation 中进行操作。
  • 重写isAsynchronous(或者isConcurrent)
@interface FSConOperation (){
BOOL _isFinished;
BOOL _isExecuting;
}

@end

@implementation FSConOperation

- (instancetype)initWithUrl:(NSString *)url completion:(dispatch_block_t)completion {
    if (self = [super init]) {
        self.url = url;
        self.responseBlock = completion;
    }

    return self;
}

// 必须重写这个主方法
- (void)main {
// 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
    @autoreleasepool {
        // 提供一个变量标识,来表示我们需要执行的操作是否完成了,当然,没开始执行之前,为NO
        BOOL taskIsFinished = NO;
        UIImage *image = nil;

        // while 保证:只有当没有执行完成和没有被取消,才执行我们自定义的相应操作
        while (taskIsFinished == NO && [self isCancelled] == NO){
            // 获取图片数据
            NSURL *url = [NSURL URLWithString:self.url];
            NSData *imageData = [NSData dataWithContentsOfURL:url];
            image = [UIImage imageWithData:imageData];
            NSLog(@"Current Thread = %@", [NSThread currentThread]);

            // 这里,设置我们相应的操作都已经完成,后面就是要通知KVO我们的操作完成了。

            taskIsFinished = YES;
        }

        // KVO 生成通知,告诉其他线程,该operation 执行完了
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        _isFinished = YES;
        _isExecuting = NO;
        [self didChangeValueForKey:@"isFinished"];
        [self didChangeValueForKey:@"isExecuting"];
        
        if (self.responseBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.responseBlock();
            });
        }
    }
}

- (void)start {
    // 如果我们取消了在开始之前,我们就立即返回并生成所需的KVO通知
    if ([self isCancelled]){
        // 我们取消了该 operation,那么就要告诉KVO,该operation已经执行完成(isFinished)
        // 这样,调用的队列(或者线程)会继续执行。
        [self willChangeValueForKey:@"isFinished"];
        _isFinished = NO;
        [self didChangeValueForKey:@"isFinished"];
    } else {
        // 没有取消,那就要告诉KVO,该队列开始执行了(isExecuting)!那么,就会调用main方法,进行同步执行。
        [self willChangeValueForKey:@"isExecuting"];
        _isExecuting = YES;
        [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
        [self didChangeValueForKey:@"isExecuting"];
    }
}

- (BOOL)isExecuting {
    return _isExecuting;
}

- (BOOL)isFinished {
    return _isFinished;
}
// 与自定义同步NSOperation不同的是,必须要实现下面的方法
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0
- (BOOL)isConcurrent {
    return YES;
}

#else

- (BOOL)isAsynchronous {
    return YES;
}
#endif

@end

使用

    FSConOperation *operation = [[FSConOperation alloc] initWithUrl:@"url" completion:^{
        //do completion
    }];
    [operation start];

GCD

了解概念
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
  • 有4个术语比较容易混淆:同步、异步、并发、串行

  • 同步和异步主要影响:能不能开启新的线程
    同步:在当前线程中执行任务,不具备开启新线程的能力
    异步:在新的线程中执行任务,具备开启新线程的能力

  • 并发和串行主要影响:任务的执行方式
    并发:多个任务并发(同时)执行
    串行:一个(当前)任务执行完毕后,再执行下一个任务

操作方式 并发队列 串行队列(手动创建) 串行队列(主队列)
同步(sync) 没有开启新线程,串行执行 没有开启新线程,串行执行 没有开启新线程,串行执行
异步(async) 有开启新线程,并发执行 有开启新线程,串行执行 没有开启新线程,串行执行
  • 在当前串行队列中使用sync函数添加任务,会卡住当前的串行队列(产生死锁)。例如在viewDidLoad中,在主线程sync插入任务A。因为FIFO,主队列中需要viewDidLoad完成后才会执行任务A,但是因为sync任务A需要立即执行。而当前队列正在执行viewDidLoad。这样viewDidLoad等待任务A,任务A等待viewDidLoad,造成死锁
GCD应用
用 dispatch_async 处理后台任务
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
        UIImage *overlayImage = [self faceOverlayImageFromImage:_image];
        dispatch_async(dispatch_get_main_queue(), ^{ // 2
            [self fadeInNewImage:overlayImage]; // 3
        });
    });
使用 dispatch_after 延后工作

一旦 dispatch_after 返回也就不能取消它,最好主线程操作

    double delayInSeconds = 1.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // 1 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2 
        if (!count) {
            [self.navigationItem setPrompt:@"Add photos with faces to Googlyify them!"];
        } else {
            [self.navigationItem setPrompt:nil];
        }
    });
使用group处理依赖
- (void)group {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });
}
使用barrier

queue必须是DISPATCH_QUEUE_CONCURRENT类型的.queue如果是global或者不是DISPATCH_QUEUE_CONCURRENT类型无效

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("test.barrier.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"dispatch_async2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:4];
       
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");
    });
}
使用apply
- (void)dispatchApplyTest {
    //生成全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    
    /*! dispatch_apply函数说明
     *
     *  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
     *         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
     *
     *  @param 10    指定重复次数  指定10次
     *  @param queue 追加对象的Dispatch Queue
     *  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
     *
     */
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });
    NSLog(@"done");
    
    /*!
     *  @brief  输出结果
     *
     2016-02-25 19:24:39.102 dispatch_apply测试[2985:165004] 0
     2016-02-25 19:24:39.102 dispatch_apply测试[2985:165086] 1
     2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 4
     2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 5
     2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 6
     2016-02-25 19:24:39.103 dispatch_apply测试[2985:165088] 3
     2016-02-25 19:24:39.104 dispatch_apply测试[2985:165004] 7
     2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] 8
     2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] 9
     2016-02-25 19:24:39.102 dispatch_apply测试[2985:165087] 2
     2016-02-25 19:24:39.105 dispatch_apply测试[2985:165004] done
     *  !!!因为在Global Dispatch Queue中执行,所以各个处理的执行时间不定
     但done一定会输出在最后的位置,因为dispatch_apply函数会等待所以的处理结束
     */
}

常见锁的使用:

为什么需要锁

未加锁

已加锁

锁的种类

OSSpinLock
  • OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
  • 目前已经不再安全,可能会出现优先级反转问题
  • 如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
  • 需要导入头文件#import

具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。

- (void)用法介绍 {
    //init
    OSSpinLock lock = OS_SPINLOCK_INIT;
    // 尝试加锁,无需等待,直接加锁,返回YES.如果需要等待就不加锁,返回NO,
    BOOL result = OSSpinLockTry(&lock);
    // 加锁
    OSSpinLockLock(&lock);
    // 解锁
    OSSpinLockUnlock(&_moneyLock);
}
os_unfair_lock
  • os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
  • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
  • 需要导入头文件#import
- (void)用法介绍 {
    //init
    os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
    // 尝试加锁,无需等待,直接加锁,返回YES.如果需要等待就不加锁,返回NO,
    BOOL result = os_unfair_lock_trylock(&lock);
    // 加锁
    os_unfair_lock_lock(&lock);
    // 解锁
    os_unfair_lock_unlock(&_moneyLock);
}
pthread_mutex
  • mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
  • 需要导入头文件#import
- (void)用法介绍 {
    //初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    /*
     * Mutex type attributes
    #define PTHREAD_MUTEX_NORMAL        0
    #define PTHREAD_MUTEX_ERRORCHECK    1
    #define PTHREAD_MUTEX_RECURSIVE        2
    #define PTHREAD_MUTEX_DEFAULT        PTHREAD_MUTEX_NORMAL
    */
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    
    //初始化锁
    pthread_mutex_t lock;
    pthread_mutex_init(&lock, &attr);
    // 尝试加锁
    pthread_mutex_trylock(&lock);
    // 加锁
    pthread_mutex_lock(&lock);
    // 解锁
    pthread_mutex_unlock(&lock);
    // 销毁资源
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_destroy(&lock);
}
pthread_mutex递归锁
#pragma mark - 递归锁
- (void)__initRecursiveMutex:(pthread_mutex_t *)mutex
{
    // 递归锁:允许同一个线程对一把锁进行重复加锁
    
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}
// 同一个线程可以重复加锁
- (void)recursiveTest {
    
    pthread_mutex_lock(&_recursive_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 10) {
        count++;
        [self recursiveTest];
    }
    
    pthread_mutex_unlock(&_recursive_mutex);
}
pthread_mutex条件锁
- (void)用法介绍 {
    // 初始化锁
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    // 初始化条件
    pthread_cond_t conditon;
    pthread_cond_init(&conditon, NULL);
    // 等待条件,进入休眠,放开锁。唤醒后再次对mutex加锁
    pthread_cond_wait(&conditon, &mutex);
    // 激活一个等待条件对的线程
    pthread_cond_signal(&conditon);
    // 激活所有等待条件得到线程
    pthread_cond_broadcast(&conditon);
    //销毁资源
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&conditon);
}
- (void)coditionTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 删除数组中的元素
- (void)__remove {
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    if (self.data.count == 0) {
        // 等待
        pthread_cond_wait(&_conditon, &_mutex);
    }
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    pthread_mutex_unlock(&_mutex);
}
// 往数组中添加元素
- (void)__add {
    pthread_mutex_lock(&_mutex);
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    pthread_cond_signal(&_conditon);
    pthread_mutex_unlock(&_mutex);
}
NSLock & NSRecursiveLock

NSLock是对mutex的普通锁封装

NSLOCK

NSRecursiveLOCK

- (void)用法介绍 {
    NSLock *lock = [[NSLock alloc] init];
    [lock tryLock];
    [lock lockBeforeDate:[NSDate distantFuture]];
    
    [lock lock];
    [lock unlock];
    
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    [recursiveLock tryLock];
    [recursiveLock lockBeforeDate:[NSDate distantFuture]];
    
    [recursiveLock lock];
    [recursiveLock unlock];
}
NSCondition

NSCondition是对mutex和cond的封装

- (void)用法介绍 {
    NSCondition *condition = [[NSCondition alloc] init];
    [condition wait];
    [condition waitUntilDate:[NSDate distantFuture]];
    [condition signal];
    [condition broadcast];
    
    [condition lock];
    [condition unlock];
}
- (void)coditionTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

- (void)__remove {
    [self.condition lock];
    NSLog(@"__remove - begin");
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    [self.condition unlock];
}

- (void)__add {
    [self.condition lock];
    sleep(1);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号
    [self.condition signal];
    sleep(2);
    [self.condition unlock];
}
NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值condition

- (void)用法介绍 {
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
    NSInteger cod = [lock condition];//readOnly
    [lock lock];//不管cod,直接加锁
    [lock lockWhenCondition:1];
    [lock tryLock];
    [lock tryLockWhenCondition:1];
    [lock lockBeforeDate:[NSDate distantFuture]];
    [lock lockWhenCondition:1 beforeDate:[NSDate distantFuture]];
    [lock unlock];//不管cod,直接解锁
    [lock unlockWithCondition:1];
}
- (void)coditionLockTest {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one {
    [self.conditionLock lock];
    NSLog(@"__one");
    sleep(1);
    [self.conditionLock unlockWithCondition:2];
}
- (void)__two {
    [self.conditionLock lockWhenCondition:2];
    NSLog(@"__two");
    sleep(1);
    [self.conditionLock unlockWithCondition:3];
}
- (void)__three {
    [self.conditionLock lockWhenCondition:3];
    NSLog(@"__three");
    [self.conditionLock unlock];
}
dispatch_semaphore
  • semaphore叫做”信号量”
  • 信号量的初始值,可以用来控制线程并发访问的最大数量
  • 信号量的初始值设为1,代表同时只允许1条线程访问资源,保证线程同步
  • 相关代码在libdispatch
- (void)用法介绍 {
    // 初始化 信号量=1,可以控制并发数量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    // 信号量-1
    // 如果信号量>0,继续执行。如果信号量<=0,当前线程休眠
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//time=等待时间
    // 信号量+1
    dispatch_semaphore_signal(self.moneySemaphore);
}
- (void)__发红包 {
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    [super __发红包];
    dispatch_semaphore_signal(self.moneySemaphore);
}
@synchronized
  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc-sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized(self) {

}
dispatch_queue(DISPATCH_QUEUE_SERIAL)

直接使用GCD的串行队列,也是可以实现线程同步的

- (void)__卖票 {
    dispatch_sync(self.moneyQueue, ^{
        [super __卖票];
    });
}
汇编指令暂存

c 下个断点
s step 一行OC代码
si stepinstruction 一行指令 汇编
nexti 函数
j 跳转 ne 条件
回车为上一个指令

性能对比
image.png

性能从高到低排序

os_unfair_lock(iOS10开始支持)
OSSpinLock(优先级反转)
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthread_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized

互斥和自旋

  • 什么情况使用自旋锁比较划算?

    • 预计线程等待锁的时间很短
    • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
    • CPU资源不紧张
    • 多核处理器
  • 什么情况使用互斥锁比较划算?

    • 预计线程等待锁的时间较长
    • 单核处理器
    • 临界区有IO操作
    • 临界区代码复杂或者循环量大
    • 临界区竞争非常激烈

读写锁(I/O)

多读单写:

  • 同一时间,只能有1个线程进行写的操作
  • 同一时间,允许有多个线程进行读的操作
  • 同一时间,不允许既有写的操作,又有读的操作
pthread_rwlock_t

等待锁的线程会进入休眠

- (void)用法介绍 {
    
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock,NULL);
    
    pthread_rwlock_rdlock(&lock);
    pthread_rwlock_tryrdlock(&lock);
    pthread_rwlock_wrlock(&lock);
    pthread_rwlock_trywrlock(&lock);
    pthread_rwlock_unlock(&lock);
    
    pthread_rwlock_destroy(&lock);
}
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    pthread_rwlock_unlock(&_lock);
}
- (void)write {
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    pthread_rwlock_unlock(&_lock);
}
dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

- (void)read {
    dispatch_async(self.queue, ^{
        sleep(1);
        NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    });
}
- (void)write 
    dispatch_barrier_async(self.queue, ^{
        sleep(1);
        NSLog(@"--%s-- IN thread:%@",__func__,[NSThread currentThread]);
    });
}

GCD原理:

其他:

参考
iOS多线程编程的几种方式
iOS进阶之多线程--NSThread详解
自定义NSOperation
彻底了解NSOperation的自定义
深入理解GCD之dispatch_group
GCD 深入理解
GNUstep
libdispatch
Jason's Blog,@synchronized 是递归锁
objc4
不再安全的 OSSpinLock

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