细数iOS中的线程同步方案

多线程安全问题

多个线程可能访问同一块资源,比如同一个文件,同一个对象,同一个变量等;当多个线程访问同一资源时,容易引发数据错乱和数据安全问题

如下面这个经典图所示,线程A、B均访问了Integer变量,但最终的结果(18)可能并不是我们想要的(19);

如果要保证共享的数据是正确的安全的,就需要使用线程同步技术:让多个线程间按顺序执行而不是并发执行;常见的线程同步技术就是加锁,同上面例子一样,加锁后能保证最终结果是正常的;

iOS中的线程同步方案常见的有以下几种:

  • pthread相关方案
  • OSSpinLock
  • os_unfair_lock
  • GCD相关方案
  • NSOperationQueue相关方案
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

pthread相关方案

pthread是跨平台的,而且更加底层;我们先来了解pthread相关的锁;

PTHREAD_MUTEX_NORMAL 普通互斥锁

互斥锁的机制:被这个锁保护的临界区就只允许一个线程进入,其它线程如果没有获得锁权限,那就只能在外面等着;等待锁的线程会处于休眠状态,处于休眠状态不会占用CPU资源;

pthread_mutex的使用:

    // 两种初始化方式
    // 1.静态初始化
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    
    // 2.动态创建
    pthread_mutex_t lock1;
    pthread_mutex_init(&lock1, NULL); // 可以根据需要配置pthread_mutexattr NULL默认为互斥锁
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        pthread_mutex_lock(&lock); // 加锁
        NSLog(@"%@===write===start",[NSThread currentThread]);
        sleep(3);
        NSLog(@"%@===write===end",[NSThread currentThread]);
        pthread_mutex_unlock(&lock); // 解锁
    }];
    
    [queue addOperationWithBlock:^{
        pthread_mutex_lock(&lock); 
        NSLog(@"%@===read===start",[NSThread currentThread]);
        sleep(2);
        NSLog(@"%@===read===end",[NSThread currentThread]);
        pthread_mutex_unlock(&lock);
    }];

两个线程任务同步执行:

     <NSThread: 0x2834863c0>{number = 5, name = (null)}===write===start
     <NSThread: 0x2834863c0>{number = 5, name = (null)}===write===end
     <NSThread: 0x2834848c0>{number = 6, name = (null)}===read===start
     <NSThread: 0x2834848c0>{number = 6, name = (null)}===read===end
     // 或
     <NSThread: 0x283486200>{number = 8, name = (null)}===read===start
     <NSThread: 0x283486200>{number = 8, name = (null)}===read===end
     <NSThread: 0x283486480>{number = 7, name = (null)}===write===start
     <NSThread: 0x283486480>{number = 7, name = (null)}===write===end

PTHREAD_MUTEX_RECURSIVE 递归锁

顾名思义,递归锁用于递归调用加锁的情况;对于递归调用的加锁,如果使用上面normal锁,则会出现死锁;递归锁就是保证了对同一把锁能多次加锁,而不用等待解锁,从而避免了递归造成的死锁问题;

- (void)synchronizedTest {
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_RECURSIVE); // PTHREAD_MUTEX_NORMAL普通互斥锁 PTHREAD_MUTEX_RECURSIVE递归锁
    pthread_mutex_init(&_lock, &att);
    pthread_mutexattr_destroy(&att);
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        [self recursiveTest:3]; // 递归调用
    }];
}

// 递归方法
- (void)recursiveTest:(NSInteger)value {
    pthread_mutex_lock(&_lock);
    
    if (value > 0) {
        NSLog(@"%@===start",[NSThread currentThread]);
        sleep(1);
        NSLog(@"%@===end",[NSThread currentThread]);
        [self recursiveTest:value-1];
    }
    
    pthread_mutex_unlock(&_lock);
}

输出正确的结果:

     <NSThread: 0x280d642c0>{number = 3, name = (null)}===start
     <NSThread: 0x280d642c0>{number = 3, name = (null)}===end
     <NSThread: 0x280d642c0>{number = 3, name = (null)}===start
     <NSThread: 0x280d642c0>{number = 3, name = (null)}===end
     <NSThread: 0x280d642c0>{number = 3, name = (null)}===start
     <NSThread: 0x280d642c0>{number = 3, name = (null)}===end

pthread_rwlock 读写锁

以上锁能很好的解决线程安全问题,但是这样的话同一时间,只会有一个线程能执行;有时我们的需求并不希望这样,比如读写操作:我们希望读是不受同步机制限制,即允许多个线程同时读;对于写,我们希望同一时间只允许一个线程操作;同时,在写操作进行时不允许同时读;而读写锁就是为这种场景而生的:
pthread_rwlock 读写锁与基本的互斥锁的创建使用方式大同小异:

    // 两种初始化方式
    // 1.静态初始化
    static pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
    
    // 2.动态创建
    static pthread_rwlock_t lock1;
    pthread_rwlock_init(&lock1, NULL);
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    for (int i = 0; i < 3; i ++) {
        [queue addOperationWithBlock:^{
            pthread_rwlock_wrlock(&lock);
            NSLog(@"%@===write===start",[NSThread currentThread]);
            sleep(3);
            NSLog(@"%@===write===end",[NSThread currentThread]);
            pthread_rwlock_unlock(&lock);
        }];
    }
    
    for (int i = 0; i < 3; i ++) {
        [queue addOperationWithBlock:^{
            pthread_rwlock_rdlock(&lock);
            NSLog(@"%@===read===start",[NSThread currentThread]);
            sleep(2);
            NSLog(@"%@===read===end",[NSThread currentThread]);
            pthread_rwlock_unlock(&lock);
        }];
    }

结果中多个read是可以并发的,write是同步执行的;

     <NSThread: 0x281b83440>{number = 5, name = (null)}===write===start
     <NSThread: 0x281b83440>{number = 5, name = (null)}===write===end
     <NSThread: 0x281b83400>{number = 6, name = (null)}===write===start
     <NSThread: 0x281b83400>{number = 6, name = (null)}===write===end
     <NSThread: 0x281b94940>{number = 4, name = (null)}===write===start
     <NSThread: 0x281b94940>{number = 4, name = (null)}===write===end
     <NSThread: 0x281b9aa00>{number = 3, name = (null)}===read===start
     <NSThread: 0x281b864c0>{number = 7, name = (null)}===read===start
     <NSThread: 0x281b87780>{number = 8, name = (null)}===read===start
     <NSThread: 0x281b87780>{number = 8, name = (null)}===read===end
     <NSThread: 0x281b9aa00>{number = 3, name = (null)}===read===end
     <NSThread: 0x281b864c0>{number = 7, name = (null)}===read===end

pthread_join

使用场景:有A,B两个线程,B线程在做某些事情之前,必须要等待A线程把事情做完,然后才能接着做下去。这时候就可以用join。

static pthread_t thread1;
static pthread_t thread2;

void * writeFunc(void *args) {
    NSLog(@"%u===write===start",(unsigned int)pthread_self());
    sleep(3);
    NSLog(@"%u===write===end",(unsigned int)pthread_self());
    pthread_exit(NULL);
    return NULL;
}

void* readFunc(void *args) {
    pthread_join(thread1, NULL);
    NSLog(@"%u===read===start",(unsigned int)pthread_self());
    sleep(2);
    NSLog(@"%u===read===end",(unsigned int)pthread_self());
    return NULL;
}

- (void)synchronizedTest {
    pthread_create(&thread1, NULL, writeFunc, NULL);
    pthread_create(&thread2, NULL, readFunc, NULL);
}

这样就保证了read一定是在write后

     871015936===write===start
     871015936===write===end
     871589376===read===start
     871589376===read===end

pthread_cond 条件锁

条件锁能在合适的时候唤醒正在等待的线程。具体什么时候合适由程序员自己控制条件变量决定;
具体的场景就是:
B线程和A线程之间有合作关系,当A线程完操作前,B线程会等待。当A线程完成后,需要让B线程知道,然后B线程从等待状态中被唤醒,然后处理自己的任务。

    // 1.静态初始化
    static pthread_cond_t cond_lock = PTHREAD_COND_INITIALIZER;
    static pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER; // 需要配合mutex互斥锁使用
    
    // 2.动态创建
    static pthread_cond_t cond_lock1;
    pthread_cond_init(&cond_lock1, NULL);
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        pthread_mutex_lock(&mutex_lock);
        while (self.condition_value <= 0) { // 条件成立则暂时解锁并等待
            pthread_cond_wait(&cond_lock, &mutex_lock);
        }
        
        NSLog(@"%@===read===start",[NSThread currentThread]);
        sleep(2);
        NSLog(@"%@===read===end",[NSThread currentThread]);
        pthread_mutex_unlock(&mutex_lock);
    }];
    
    [queue addOperationWithBlock:^{
        pthread_mutex_lock(&mutex_lock);
        NSLog(@"%@===write===start",[NSThread currentThread]);
        sleep(3);
        self.condition_value = 1; // 一定要更改条件 否则上面read线程条件成立又会wait
        NSLog(@"%@===write===end",[NSThread currentThread]);
        
        pthread_cond_signal(&cond_lock); // 传递信号给等待的线程 而且是在解锁前
//        pthread_cond_broadcast(pthread_cond_t * _Nonnull) // 通知所有线程
        
        pthread_mutex_unlock(&mutex_lock);
    }];
     <NSThread: 0x283783e40>{number = 3, name = (null)}===write===start
     <NSThread: 0x283783e40>{number = 3, name = (null)}===write===end
     <NSThread: 0x28379aa40>{number = 4, name = (null)}===read===start
     <NSThread: 0x28379aa40>{number = 4, name = (null)}===read===end

这里有几个需要注意的地方:

  • 一定要配合互斥锁使用;
  • 一定要判断条件并更改条件;
  • 最好使用while做条件判断(而不是if)
  • 发送信号时,最好在临近区内发送(即互斥锁范围内);

以上几点的原因,可以参考下面大神的文章;

semaphore 信号量

信号量维护了一个unsigned int类型的value,通过这个值控制线程同步;具体有以下使用场景:

  • 信号量的初始值设为1,代表同时只允许1条线程访问资源,保证线程同步
    // 创建 原型sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
    // name 信号的外部名字
    // oflag 选择创建或打开一个现有的信号灯
    // mode 权限位
    // value 信号初始值
    sem_t * sem = sem_open("semname", O_CREAT, 0644, 1);
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        sem_wait(sem); // 首先判断信号量value 如果=0则等待,否则value-1并正常往下走
        NSLog(@"%@===write===start",[NSThread currentThread]);
        sleep(3);
        NSLog(@"%@===write===end",[NSThread currentThread]);
        sem_post(sem); // 执行完发送信号,value+1
    }];

    [queue addOperationWithBlock:^{
        sem_wait(sem);
        NSLog(@"%@===read===start",[NSThread currentThread]);
        sleep(2);
        NSLog(@"%@===read===end",[NSThread currentThread]);
        sem_post(sem);
    }];
  • 信号量的初始值value,可以用来控制线程并发访问的最大数量
sem_t *sem = sem_open("semname_count", O_CREAT, 0644, 3);

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i = 0; i < 21; i ++) {
        [queue addOperationWithBlock:^{
            sem_wait(sem);
            NSLog(@"%@===write===start",[NSThread currentThread]);
            sleep(2);
            NSLog(@"%@===write===end",[NSThread currentThread]);
            sem_post(sem);
        }];
    }

输出结果可以看出最多最会有3个线程:

     <NSThread: 0x280431380>{number = 6, name = (null)}===write===start
     <NSThread: 0x28040cb80>{number = 5, name = (null)}===write===start
     <NSThread: 0x280431500>{number = 7, name = (null)}===write===start
     <NSThread: 0x28040cb80>{number = 5, name = (null)}===write===end
     <NSThread: 0x28040cb80>{number = 5, name = (null)}===write===start
     <NSThread: 0x280431380>{number = 6, name = (null)}===write===end
     <NSThread: 0x280431380>{number = 6, name = (null)}===write===start

以上代码,其实就类似设置NSOperationQueue的maxConcurrentOperationCount效果;

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 3;

OSSpinLock自旋锁

自旋锁的作用同互斥锁一样,不同于互斥锁的线程休眠机制,自旋锁等待的线程会忙等,也就是等待的过程其实是在跑一个while循环;这样等待的过程同样消耗CPU资源,但这种方式不会涉及线程唤醒、休眠的切换,性能会高点;

__block OSSpinLock lock = OS_SPINLOCK_INIT;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
    OSSpinLockLock(&lock);
    NSLog(@"%@===write===start",[NSThread currentThread]);
    sleep(3);
    NSLog(@"%@===write===end",[NSThread currentThread]);
    OSSpinLockUnlock(&lock);
}];
[queue addOperationWithBlock:^{
    OSSpinLockLock(&lock);
    NSLog(@"%@===read===start",[NSThread currentThread]);
    sleep(2);
    NSLog(@"%@===read===end",[NSThread currentThread]);
    OSSpinLockUnlock(&lock);
}];

同样能同步执行,但代码会有警告:

'OSSpinLockUnlock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock_unlock() from <os/lock.h> instead

这是因为OSSpinLock已经不再安全了,会有优先级反转问题;
多线程并发处理,原理上说是CPU时间片轮转机制,即将时间划分为极小单位,每个线程依次执行这极段的时间;这样多个线程看起来是同时执行的;另外,不同的线程有可能是不同的优先级;高优先级的线程要占用较长的时间、CPU资源;高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。
如果使用自旋锁,且一个低优先级的线程先于高优先级的线程获得锁并访问共享资源;同时高优先级的线程也会尝试获取锁,获取锁失败就一直忙等,忙等状态占用大量CPU资源;而低优先级的线程也需要CPU资源,但是竞争不过从而导致任务迟迟完不成,无法解锁;

苹果给的建议是使用os_unfair_lock替代,但这个最低只支持iOS10;

    __block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; // 初始化
    os_unfair_lock_lock(&lock); // 加锁
    os_unfair_lock_unlock(&lock); // 解锁

NSLock

这个其实就是对pthread_mutex普通互斥锁的封装;面向对象,使用起来更方便;

- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

NSRecursiveLock 递归锁

对pthread_mutex递归锁的封装,方法和上面的一样;

NSCondition

对pthread_cond条件锁的封装,使用pthread_cond需要配合pthread_mutex互斥锁使用,NSCondition封装好了,一把锁就能实现:

    NSCondition *lock = [[NSCondition alloc] init];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        [lock lock];
        
        while (self.condition_value <= 0) { // 条件成立则暂时解锁并等待
            [lock wait];
        }
        
        NSLog(@"%@===read===start",[NSThread currentThread]);
        sleep(2);
        NSLog(@"%@===read===end",[NSThread currentThread]);
        
        [lock unlock];
    }];
    
    [queue addOperationWithBlock:^{
        [lock lock];
        
        NSLog(@"%@===write===start",[NSThread currentThread]);
        sleep(3);
        self.condition_value = 1; // 一定要更改条件 否则上面read线程条件成立又会wait
        NSLog(@"%@===write===end",[NSThread currentThread]);
        
        [lock signal]; // 传递信号给等待的线程 而且是在解锁前
//        [lock broadcast] // 通知所有线程
        
        [lock unlock];
    }];

NSConditionLock

对NSCondition的进一步封装,在NSCondition基础上,加了可控制的条件condition;通过条件变量,控制通知哪条线程;

@property (readonly) NSInteger condition;
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1]; // 初始化,设置condition=1
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        [lock lockWhenCondition:1]; // 当condition=1时 获取锁成功 否则等待(但是首次使用lockWhenCondition时condition不对时也能获取锁成功)
        
        NSLog(@"%@===A===start",[NSThread currentThread]);
        sleep(2);
        NSLog(@"%@===A===end",[NSThread currentThread]);
        
        // unlock根据不同的条件 控制对应的线程
        [lock unlockWithCondition:2]; // 解锁,同时设置condition=2并signal;
//        [lock unlockWithCondition:3];
    }];
    
    [queue addOperationWithBlock:^{
        [lock lockWhenCondition:2];
        
        NSLog(@"%@===B===start",[NSThread currentThread]);
        sleep(1);
        NSLog(@"%@===B===end",[NSThread currentThread]);
        
        [lock unlock];
    }];
    
    [queue addOperationWithBlock:^{
        [lock lockWhenCondition:3];
        
        NSLog(@"%@===C===start",[NSThread currentThread]);
        sleep(1);
        NSLog(@"%@===C===end",[NSThread currentThread]);
        
        [lock unlock];
    }];

线程A解锁时可以传不同条件值,对应条件值的其他等待线程就会被唤醒;这里条件值为2,则执行线程B任务;条件设置为3,则执行线程C任务;如果是其他值则线程B,C继续一直等待;

NSThread: 0x282b66340>{number = 6, name = (null)}===A===start
NSThread: 0x282b66340>{number = 6, name = (null)}===A===end
NSThread: 0x282b68240>{number = 3, name = (null)}===B===start
NSThread: 0x282b68240>{number = 3, name = (null)}===B===end

@synchronized

是对mutex递归锁的封装;
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作;一个对象对应一把锁;

    NSObject *obj = [[NSObject alloc] init];
    @synchronized (obj) {
        // ...
    }

GCD相关

dispatch_semaphore信号量

这个和上篇讲的semaphore差不多;

// 创建信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
// 判断信号量,如果=0则等待,否则信号值-1往下执行
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
// 发送信号量,信号值+1
dispatch_semaphore_signal(sem);

DISPATCH_QUEUE_SERIAL 串行队列

串行队列的任务就是同步执行的;

dispatch_queue_t queue = dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    // ThreadA dosomething....
});
dispatch_async(queue, ^{
    // ThreadB dosomething....
});

dispatch_group

将任务分组,组内任务异步执行;当所有任务执行完后,可以通知其他线程执行任务:

    // group必须使用自己创建的并发队列 使用global全局队列无效 
    dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); xxx
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"%@===TaskA",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"%@===TaskB",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"%@===TaskC",[NSThread currentThread]);
    });
//    dispatch_async(queue, ^{
//        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC))); // 可以设置等待的超时时间
//        NSLog(@"%@===TaskC",[NSThread currentThread]);
//    });

以上代码对应的场景就是:A,B线程可以并发执行,但C线程一定要在AB线程执行完后再执行;
dispatch_group_notify也可以使用dispatch_group_wait替代,一样是阻塞的作用,而dispatch_group_wait能设置等待超时时间;超过时间将不再阻塞,继续任务;
还有一点需要注意的是,dispatch_group必须使用自己创建的并发队列, 使用global全局队列无效,使用串行队列没有意义;

dispatch_barrier

如同它的名字一样,dispatch_barrier就是起到一个栅栏的作用;栅栏两边的任务可以并发执行,栅栏里的任务必须等到栅栏上边的任务执行完才执行,栅栏下边的任务必须等栅栏里的任务执行完后才执行;
dispatch_barrier其实就是阻塞队列的作用;
这个其实也可以通过dispatch_group实现,但dispatch_barrier更加方便;

    dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"%@===TaskA",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"%@===TaskB",[NSThread currentThread]);
    });
    
    // async不会阻塞当前线程(主线程)
    dispatch_barrier_async(queue, ^{
        NSLog(@"%@===Barrier",[NSThread currentThread]);
    });
    // sync会阻塞当前队列(主队列)
//    dispatch_barrier_sync(queue, ^{
//        NSLog(@"%@===Barrier",[NSThread currentThread]);
//    });
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"%@===TaskC",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"%@===TaskD",[NSThread currentThread]);
    });

    NSLog(@"%@===MainTask",[NSThread currentThread]);
<NSThread: 0x2816bbc40>{number = 1, name = main}===MainTask
<NSThread: 0x2816fe440>{number = 3, name = (null)}===TaskB
<NSThread: 0x2816877c0>{number = 4, name = (null)}===TaskA
<NSThread: 0x2816877c0>{number = 4, name = (null)}===Barrier
<NSThread: 0x2816fe440>{number = 3, name = (null)}===TaskD
<NSThread: 0x2816877c0>{number = 4, name = (null)}===TaskC

dispatch_barrier的使用有两种方式

  • dispatch_barrier_async
  • dispatch_barrier_sync

async不会阻塞当前队列,sync同时会阻塞当前队列;如果以上代码换成dispatch_barrier_sync,最终的结果将是MainTask会在Barrier任务后;

基于barrier的这种特性,很容易实现一个读写锁;栅栏内为write,栅栏外为read;这样同样能实现读任务能异步执行,写任务只能同步执行;同时在写操作时,不允许读操作;

    dispatch_queue_t queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 3; i ++) {
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"%@===read",[NSThread currentThread]);
        });
    }
    
    for (int i = 0; i < 3; i ++) {
        dispatch_barrier_async(queue, ^{
            sleep(1);
            NSLog(@"%@===write",[NSThread currentThread]);
        });
    }
    
    for (int i = 0; i < 3; i ++) {
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"%@===read",[NSThread currentThread]);
        });
    }
<NSThread: 0x282a04880>{number = 4, name = (null)}===read
 <NSThread: 0x282a13100>{number = 6, name = (null)}===read
 <NSThread: 0x282a050c0>{number = 5, name = (null)}===read
 <NSThread: 0x282a4ee40>{number = 1, name = main}===write
 <NSThread: 0x282a4ee40>{number = 1, name = main}===write
 <NSThread: 0x282a4ee40>{number = 1, name = main}===write
 <NSThread: 0x282a050c0>{number = 5, name = (null)}===read
 <NSThread: 0x282a13400>{number = 7, name = (null)}===read
 <NSThread: 0x282a04880>{number = 4, name = (null)}===read

NSOperation相关

NSOperation是对GCD的封装

最大并发数
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 最大并发数设置为1,queue内任务同步执行
queue.maxConcurrentOperationCount = 1;
设置栅栏
// similarly to the `dispatch_barrier_async` function.
[queue addBarrierBlock:^{

}];
设置依赖关系

使用场景:线程B必须要等线程A任务执行完后才执行,即线程A依赖线程B:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *taskA = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"%@===TaskA",[NSThread currentThread]);
    }];
    NSBlockOperation *taskB = [NSBlockOperation blockOperationWithBlock:^{
        sleep(.5);
        NSLog(@"%@===TaskB",[NSThread currentThread]);
    }];
    [taskB addDependency:taskA];
    
    [queue addOperation:taskA];
    [queue addOperation:taskB];
<NSThread: 0x281af5bc0>{number = 6, name = (null)}===TaskA
 <NSThread: 0x281af5bc0>{number = 6, name = (null)}===TaskB

自旋锁、互斥锁比较

前面我们介绍了自旋锁、互斥锁机制的不同,它们各有优点;实际开发中的如何选择呢?

适用自旋锁的情况

  • 线程等待时间比较短(这样忙等的时间不会太长,不会有太大消耗)
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张(自旋锁比较耗CPU资源)

相反的,适用互斥锁的情况

  • 线程等待时间比较长
  • 加锁的代码(临界区)复杂,循环度大,或者有IO操作
  • 加锁的代码(临界区)竞争激烈

线程同步方案性能比较

这个直接引用大神的图:

另外,os_unfair_lock锁性能是最好的,可惜最低只支持iOS10;


完整demo

参考:
pthread的各种同步机制
不再安全的 OSSpinLock

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268