线程安全与锁

IOS编码中,锁的出现其实是因为多线程安全的问题。那么,问题来了,什么是线程安全?为什么锁可以解决线程安全问题?单线程是不是绝对的线程安全?iOS编程有多少种锁?加解锁的效率如何?......

各种锁的性能图

什么线程安全?

多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。

NSInteger total = 0;
- (void)threadNotSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
        });
    }
}
//第一次输出:
2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 1
2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3
2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2
2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2
2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1
2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0
//第二次输出
2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1
2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2
2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3
2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2
2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1
2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0
NSInteger total = 0;
NSLock *lock = [NSLock new];
- (void)threadSafe {
    for (NSInteger index = 0; index < 3; index++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [lock lock];
            total += 1;
            NSLog(@"total: %ld", total);
            total -= 1;
            NSLog(@"total: %ld", total);
            [lock unlock];
        });
    }
}
//第一次输出
2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 1
2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0
2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1
2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0
2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1
2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0
//第二次输出
2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1
2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0
2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1
2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0
2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1
2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0

第一个函数和第一次和第二次调用的函数不一样,换句话说,不能够确认代码的执行顺序和结果,是线程不安全的;第二个函数的第一次和第二次输出结果是一样的,可以确定函数的执行结果,是线程安全的。

线程不安全是有多线程访问造成的,那么如何解决?

  • 既然线程安全问题是由多线程引起的,那么最极端的做法使用单线程保证线程安全。
  • 线程安全是由于多线程访问修改共享资源引起不可预测的结果,因此,如果都是访问共享资源而不去修改共享资源也可以保证线程安全,比如:设置属性的全局变量。
  • 使用锁。

自旋锁(OSSpinLock)

#pragma mark -  自旋锁,已经被废弃
- (void)ossPinLockConfig{
    osslock = OS_SPINLOCK_INIT;
    tickets = 5;
   // 线程1

    dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_PRIORITY_DEFAULT);
    
    dispatch_async(diapatchQueue0, ^{
        
        [self saleTickets];

    });

    //线程2
    dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(diapatchQueue1, ^{
        
        [self saleTickets];

        
    });
}

- (void)saleTickets {
    
    while (1) {
        [NSThread sleepForTimeInterval:1];
        //加锁
        OSSpinLockLock(&osslock);
        
        if (tickets > 0) {
            tickets--;
            NSLog(@"剩余票数= %ld, Thread:%@",tickets,[NSThread currentThread]);
            
        } else {
            NSLog(@"票卖完了  Thread:%@",[NSThread currentThread]);
            break;
        }
        //解锁
        OSSpinLockUnlock(&osslock);
    }
    
}

输出结果

2018-01-02 16:11:15.727776+0800 LockTest[3136:239407] 剩余票数= 4, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:15.727970+0800 LockTest[3136:239404] 剩余票数= 3, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:16.732482+0800 LockTest[3136:239407] 剩余票数= 2, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:16.732632+0800 LockTest[3136:239404] 剩余票数= 1, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:17.735044+0800 LockTest[3136:239407] 剩余票数= 0, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:17.735261+0800 LockTest[3136:239404] 票卖完了  Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}

自旋锁面临的问题不再安全的 OSSpinLock

OSSpinLock存在的bug:新版iOS 中,系统维护了 5 个不同的线程优先级/QoS: backgroundutilitydefaultuser-initiateduser-interactive高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock

具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于spin lock 的忙等状态从而占用大量CPU。此时低优先级线程无法与高优先级线程争夺CPU 时间,从而导致任务迟迟完不成、无法释放lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock

苹果工程师 Greg Parker提到,对于这个问题,一种解决方案是用 truly unbounded backoff算法,这能避免livelock问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是libobjc目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。

libobjc里用的是Mach 内核的 thread_switch()然后传递了一个 mach thread port 来避免优先级反转,另外它还用了一个私有的参数选项,所以开发者无法自己实现这个锁。另一方面,由于二进制兼容问题,OSSpinLock 也不能有改动。

最终的结论就是,除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了。

模拟OSSpinLock高低优先级场景

#pragma mark -  自旋锁,已经被废弃
- (void)ossPinLockConfig{
    osslock = OS_SPINLOCK_INIT;
    tickets = 1000;
   // 线程1

//    dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t diapatchQueue0 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(diapatchQueue0, ^{
        
        [self saleTickets:@"2"];

    });
    
    //线程2
//    dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t diapatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    dispatch_async(diapatchQueue1, ^{
        
        [self saleTickets:@"1"];

        
    });
    
    dispatch_queue_t diapatchQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(diapatchQueue2, ^{
        
        [self saleTickets:@"3"];
        
        
    });
    
    dispatch_queue_t diapatchQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(diapatchQueue3, ^{
        
        [self saleTickets:@"4"];
        
        
    });
    
for (NSInteger i = 0; i < 100000000; i ++) {
        dispatch_queue_t diapatchQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_async(diapatchQueue4, ^{
            
            [self saleTickets:@"5"];
            
            
        });
    }
}

- (void)saleTickets:(NSString *)flag {
    
    while (1) {
        [NSThread sleepForTimeInterval:1];
        //加锁
        OSSpinLockLock(&osslock);
        
        if (tickets > 0) {
            tickets--;
            
            // 如果flag 等于1的情况下阻塞线程
            if ([flag isEqualToString:@"1"]) {
              sleep(4);
            }
            

            NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
            
        } else {
            NSLog(@"票卖完了  Thread:%@-flag:%@",[NSThread currentThread],flag);
            break;
        }
        //解锁
        OSSpinLockUnlock(&osslock);
    }
    
}

我们模拟1亿个人去访问同一个资源,其中flag = 1的线程是低优先级的线程,在资源访问的时候,我们又对线程1访问资源时候做一个线程阻塞4s,观察CPU ReportUsage ComParison Other processes 的变化,在阻塞的过程中Other processes占比会猛然增加,说明其余优先级高的线程一直处于等待状态而不是休眠状态,激烈地争夺CPU的资源。

dispatch_semaphore 信号量

GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”(从本质意义上讲,信号量与锁是有区别)。

dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程1 等待ing");
    dispatch_semaphore_wait(signal, overTime); //signal 值 -1
    NSLog(@"线程1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"线程1 发送信号");
    NSLog(@"--------------------------------------------------------");
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程2 等待ing");
    dispatch_semaphore_wait(signal, overTime);
    NSLog(@"线程2");
    dispatch_semaphore_signal(signal);
    NSLog(@"线程2 发送信号");
});
dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1

关于信号量,我们可以用停车来比喻:

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

2018-01-02 18:47:21.055284+0800 LockTest[6884:391804] 线程1 等待ing
2018-01-02 18:47:21.055288+0800 LockTest[6884:391802] 线程2 等待ing
2018-01-02 18:47:21.055414+0800 LockTest[6884:391802] 线程2
2018-01-02 18:47:21.055501+0800 LockTest[6884:391802] 线程2 发送信号
2018-01-02 18:47:21.055507+0800 LockTest[6884:391804] 线程1
2018-01-02 18:47:21.055576+0800 LockTest[6884:391804] 线程1 发送信号
2018-01-02 18:47:21.056410+0800 LockTest[6884:391804] --------------------------------------------------------

初始信号量大于0
可以发现,因为我们初始化信号量的时候是大于 0 的,所以并没有阻塞线程,而是直接执行了 线程1 线程2。

我们把 信号量初始值改为 0:

dispatch_semaphore_t signal = dispatch_semaphore_create(0);

运行结果:

2018-01-02 18:50:19.370792+0800 LockTest[6971:396072] 线程1 等待ing
2018-01-02 18:50:19.370792+0800 LockTest[6971:396321] 线程2 等待ing
2018-01-02 18:50:29.371276+0800 LockTest[6971:396321] 线程2
2018-01-02 18:50:29.371276+0800 LockTest[6971:396072] 线程1
2018-01-02 18:50:29.371448+0800 LockTest[6971:396321] 线程2 发送信号
2018-01-02 18:50:29.371451+0800 LockTest[6971:396072] 线程1 发送信号
2018-01-02 18:50:29.371584+0800 LockTest[6971:396072] --------------------------------------------------------

互斥锁

synchronized互斥锁性能最差,不推荐使用。

@synchronized(这里添加一个OC对象,一般使用self) {
       这里写要加锁的代码
  }
 注意点
   1.加锁的代码尽量少
   2.添加的OC对象必须在多个线程中都是同一对象
    3.优点是不需要显式的创建锁对象,便可以实现锁的机制。
    4. @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

- (void)synchronizedSaleTickets:(NSString *)flag {
    
    while (1) {
        
        @synchronized(self){
            
            [NSThread sleepForTimeInterval:1];

            if (tickets > 0) {
                
                tickets--;

                NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
                
            } else {
                NSLog(@"票卖完了  Thread:%@-flag:%@",[NSThread currentThread],flag);
                break;
            }
        }
        
    }
    
}

pthred_mutex 互斥锁

#import <pthread.h>
- (void)pthread_mutex{
    tickets = 20;
    
//    __block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    
    //1.线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self pthread_mutexSaleTickets:@"1"];
    });
    
    //1.线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self pthread_mutexSaleTickets:@"2"];
    });
}

- (void)pthread_mutexSaleTickets:(NSString *)flag {
    
    while (1) {
        
        
        [NSThread sleepForTimeInterval:1];
        pthread_mutex_lock(&mutex);
        
        if (tickets > 0) {
            
            tickets--;
            
            NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
            
        } else {
            NSLog(@"票卖完了  Thread:%@-flag:%@",[NSThread currentThread],flag);
            break;
        }
        
        pthread_mutex_unlock(&mutex);
        }
        
}
2018-01-03 14:18:45.837111+0800 LockTest[1680:165345] 剩余票数= 19, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837307+0800 LockTest[1680:165346] 剩余票数= 18, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837481+0800 LockTest[1680:165345] 剩余票数= 17, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837581+0800 LockTest[1680:165346] 剩余票数= 16, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837720+0800 LockTest[1680:165345] 剩余票数= 15, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837868+0800 LockTest[1680:165346] 剩余票数= 14, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837978+0800 LockTest[1680:165345] 剩余票数= 13, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838052+0800 LockTest[1680:165346] 剩余票数= 12, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838117+0800 LockTest[1680:165345] 剩余票数= 11, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838352+0800 LockTest[1680:165346] 剩余票数= 10, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838499+0800 LockTest[1680:165345] 剩余票数= 9, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840398+0800 LockTest[1680:165346] 剩余票数= 8, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840495+0800 LockTest[1680:165345] 剩余票数= 7, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840584+0800 LockTest[1680:165346] 剩余票数= 6, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840694+0800 LockTest[1680:165345] 剩余票数= 5, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840830+0800 LockTest[1680:165346] 剩余票数= 4, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840933+0800 LockTest[1680:165345] 剩余票数= 3, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841050+0800 LockTest[1680:165346] 剩余票数= 2, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841141+0800 LockTest[1680:165345] 剩余票数= 1, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841310+0800 LockTest[1680:165346] 剩余票数= 0, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841386+0800 LockTest[1680:165345] 票卖完了  Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2

NSLock 普通锁

lock、unlock:不多做解释,和上面一样
trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
- (void)nslock{
    tickets = 20;
    
    lock = [[NSLock alloc] init];
    
    //1.线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self nslockSaleTickets:@"1"];
    });
    
    //1.线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self nslockSaleTickets:@"2"];
    });
}

- (void)nslockSaleTickets:(NSString *)flag {
    
    while (1) {
        
        
        [NSThread sleepForTimeInterval:1];
        [lock lock];
        
        if (tickets > 0) {
            
            tickets--;
            
            NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
            
        } else {
            NSLog(@"票卖完了  Thread:%@-flag:%@",[NSThread currentThread],flag);
            break;
        }
        
        [lock unlock];
    }
    
}

//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程2 尝试加速ing...");
    BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    if (x) {
        NSLog(@"线程2");
        [lock unlock];
    }else{
        NSLog(@"失败");
    }
});

NSCondition

wait:进入等待状态
waitUntilDate::让一个线程等待一定的时间
signal:唤醒一个等待的线程
broadcast:唤醒所有等待的线程

让一个线程等待2s

NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"start");
    [cLock lock];
    [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    NSLog(@"线程1");
    [cLock unlock];
});

waiting 2秒,唤醒一个等待线程

NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程1加锁成功");
    [cLock wait];
    NSLog(@"线程1");
    [cLock unlock];
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程2加锁成功");
    [cLock wait];
    NSLog(@"线程2");
    [cLock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒一个等待的线程");
    [cLock signal];
});

唤醒所有等待的线程

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒所有等待的线程");
    [cLock broadcast];
});

NSRecursiveLock 递归锁

递归锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。

NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveBlock)(int);
    RecursiveBlock = ^(int value) {
        [rLock lock];
        if (value > 0) {
            NSLog(@"线程%d", value);
            RecursiveBlock(value - 1);
        }
        [rLock unlock];
    };
    RecursiveBlock(4);
});
2018-01-03 14:40:44.206656+0800 LockTest[1816:185465] 线程4

这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个 block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。

NSLock 替换为 NSRecursiveLock:

- (void)NSRecursiveLock{
    NSRecursiveLock *rLock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [rLock lock];
            if (value > 0) {
                NSLog(@"线程%d", value);
                RecursiveBlock(value - 1);
            }
            [rLock unlock];
        };
        RecursiveBlock(4);
    });
}
2018-01-03 14:42:03.128195+0800 LockTest[1852:187555] 线程4
2018-01-03 14:42:03.128399+0800 LockTest[1852:187555] 线程3
2018-01-03 14:42:03.128618+0800 LockTest[1852:187555] 线程2
2018-01-03 14:42:03.128818+0800 LockTest[1852:187555] 线程1

NSConditionLock 条件锁

相比于 NSLock多了个 condition参数,我们可以理解为一个条件标示。

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];

//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    if([cLock tryLockWhenCondition:0]){
        NSLog(@"线程1");
       [cLock unlockWithCondition:1];
    }else{
         NSLog(@"失败");
    }
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:3];
    NSLog(@"线程2");
    [cLock unlockWithCondition:2];
});

//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lockWhenCondition:1];
    NSLog(@"线程3");
    [cLock unlockWithCondition:3];
});
2018-01-03 14:50:58.893535+0800 LockTest[1892:194965] 线程1
2018-01-03 14:50:58.893826+0800 LockTest[1892:194973] 线程3
2018-01-03 14:50:58.894022+0800 LockTest[1892:194966] 线程2

我们在初始化 NSConditionLock 对象时,给了他的标示为 0
执行 tryLockWhenCondition:时,我们传入的条件标示也是 0,所 以线程1 加锁成功
执行 unlockWithCondition:时,这时候会把condition由 0 修改为 1
因为condition 修改为了 1, 会先走到 线程3,然后 线程3 又将 condition 修改为 3
最后 走了 线程2 的流程

参考文章

iOS 开发中的八种锁(Lock)
不再安全的 OSSpinLock
iOS 中几种常用的锁总结

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容