美文网首页
iOS 线程锁的使用

iOS 线程锁的使用

作者: 2525252472 | 来源:发表于2019-01-11 14:10 被阅读0次

    一、线程锁相关概念

    线程锁:我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要保证同一个时间只允许有限个线程访问这一块资源,所有就需要线程锁,锁也分为自旋锁,互斥锁,信号量等等。比如一个线程 A 进入需要保护代码之前添加简单的互斥锁,另一个线程 B 就无法访问,只有等待前一个线程 A 执行完被保护的代码后解锁,B 线程才能访问被保护代码。

    临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。

    时间片(timeslice):分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。现代操作系统(如:Windows、Linux、Mac OS X等)允许同时运行多个进程 —— 例如,你可以在打开音乐播放器听音乐的同时用浏览器浏览网页并下载文件。事实上,由于一台计算机通常只有一个CPU,所以永远不可能真正地同时运行多个任务。这些进程“看起来像”同时运行的,实则是轮番穿插地运行,由于时间片通常很短(在Linux上为5ms-800ms),用户不会感觉到。

    自旋锁:是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等(busy-wait)状态。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的,如果临界区的执行时间过长,使用自旋锁不是个好主意。

    互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。

    读写锁:是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。

    信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。信号量的值为零时,会使线程进入睡眠状态,主动让出时间片,主动让出时间片并不总是代表效率高。让出时间片会导致操作系统切换到另一个线程,这种上下文切换通常需要 10 微秒左右,而且至少需要两次切换。如果等待时间很短,比如只有几个微秒,忙等就比线程睡眠更高效。

    条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。

    二、iOS中的十一种锁

    1.OSSpinLock(自旋锁)
    2.os_unfair_lock(iOS 10以后,苹果提供用来代替OSSpinLock)
    3.dispatch_semaphore (信号量)
    4.pthread_mutex(互斥锁)
    5.pthread_mutex(recursive) (递归锁)
    6.NSLock(互斥锁)
    7.NSCondition(条件锁)
    8.NSRecursiveLock(递归锁)
    9.NSConditionLock(条件锁)
    10.@synchronized(条件锁)
    11.pthread_rwlock(读写锁)

    1.OSSpinLock(自旋锁)

    测试中效率最高的锁, 不过经YYKit作者确认, OSSpinLock已经不再线程安全,OSSpinLock有潜在的优先级反转问题.不再安全的 OSSpinLock在 iOS 10/macOS 10.12 发布时,苹果提供了新的 os_unfair_lock 作为 OSSpinLock 的替代,并且将 OSSpinLock 标记为了 Deprecated。

    OS_SPINLOCK_INIT: 默认值为 0,在 locked 状态时就会大于 0,unlocked状态下为 0
    OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock 地址
    OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock 地址
    OSSpinLockTry(&oslock):尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO

    OSSpinLockLock测试代码

    #import <libkern/OSAtomic.h>
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //设置票数
        self.totalCount = 5;
        spinLock = OS_SPINLOCK_INIT;
        dispatch_queue_t queue = dispatch_queue_create("saleTicket", DISPATCH_QUEUE_CONCURRENT);
        //售票线程1
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程A";
            [self saleTicket];
        });
        NSLog(@"-----------------------");
        //售票线程2
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程B";
            [self saleTicket];
        });
    
    }
    -(void)saleTicket{
        while (1) {
    //        [NSThread sleepForTimeInterval:1];
            NSLog(@"%@准备上锁",[NSThread currentThread].name);
            OSSpinLockLock(&spinLock);
            NSLog(@"%@已上锁",[NSThread currentThread].name);
            if (self.totalCount > 0) {
                self.totalCount --;
                NSLog(@"%@卖出去了一张票,还剩下%zd张票", [NSThread currentThread].name,self.totalCount);
                OSSpinLockUnlock(&spinLock);
                NSLog(@"%@解锁",[NSThread currentThread].name);
            }else{
                NSLog(@"%@:票已售完",[NSThread currentThread].name);
                OSSpinLockUnlock(&spinLock);
                NSLog(@"%@解锁",[NSThread currentThread].name);
                break;
            }
        }
    }
    

    输出结果(每次执行结果不相同)

    11:32:06.352257+0800 threadTest[2739:696809] 售票线程A准备上锁
    11:32:06.352282+0800 threadTest[2739:696809] 售票线程A已上锁
    11:32:06.352614+0800 threadTest[2739:696809] 售票线程A卖出去了一张票,还剩下2张票
    11:32:06.353249+0800 threadTest[2739:696809] 售票线程A解锁
    11:32:06.353371+0800 threadTest[2739:696809] 售票线程A准备上锁
    11:32:06.352793+0800 threadTest[2739:696981] 售票线程B准备上锁
    11:32:06.353399+0800 threadTest[2739:696809] 售票线程A已上锁
    11:32:06.353429+0800 threadTest[2739:696809] 售票线程A卖出去了一张票,还剩下1张票
    11:32:06.353464+0800 threadTest[2739:696809] 售票线程A解锁
    11:32:06.353496+0800 threadTest[2739:696809] 售票线程A准备上锁
    11:32:06.353656+0800 threadTest[2739:696981] 售票线程B已上锁
    11:32:06.353685+0800 threadTest[2739:696981] 售票线程B卖出去了一张票,还剩下0张票
    11:32:06.353711+0800 threadTest[2739:696981] 售票线程B解锁
    11:32:06.353741+0800 threadTest[2739:696981] 售票线程B准备上锁
    11:32:06.353772+0800 threadTest[2739:696981] 售票线程B已上锁
    11:32:06.356014+0800 threadTest[2739:696981] 售票线程B:票已售完
    11:32:06.356058+0800 threadTest[2739:696981] 售票线程B解锁
    11:32:06.356621+0800 threadTest[2739:696809] 售票线程A已上锁
    11:32:06.357062+0800 threadTest[2739:696809] 售票线程A:票已售完
    11:32:06.357103+0800 threadTest[2739:696809] 售票线程A解锁
    

    OSSpinLockTry 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO

    -(void)saleTicket2{
        while (1) {
    //        [NSThread sleepForTimeInterval:1];
            NSLog(@"%@准备上锁",[NSThread currentThread].name);
    //        OSSpinLockLock(&spinLock);
            
            //尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
            //当前线程锁失败,也可以继续其它任务,用 OSSpinLockTry 合适
            //当前线程只有锁成功后,才会做一些有意义的工作,那就OSSpinLockLock,没必要轮询OSSpinLockTry
            if (OSSpinLockTry(&spinLock)) {
                NSLog(@"%@已上锁",[NSThread currentThread].name);
                if (self.totalCount > 0) {
                    self.totalCount --;
                    NSLog(@"%@卖出去了一张票,还剩下%zd张票", [NSThread currentThread].name,self.totalCount);
                    OSSpinLockUnlock(&spinLock);
                    NSLog(@"%@解锁",[NSThread currentThread].name);
                }else{
                    NSLog(@"%@:票已售完",[NSThread currentThread].name);
                    OSSpinLockUnlock(&spinLock);
                    NSLog(@"%@解锁",[NSThread currentThread].name);
                    break;
                }
            }else{
                NSLog(@"%@等待中",[NSThread currentThread].name);
            }
        }
    }
    

    输出结果

    13:53:40.635663+0800 threadTest[296:19536] 售票线程A准备上锁
    13:53:40.635689+0800 threadTest[296:19536] 售票线程A已上锁
    13:53:40.635717+0800 threadTest[296:19536] 售票线程A卖出去了一张票,还剩下2张票
    13:53:40.635929+0800 threadTest[296:19536] 售票线程A解锁
    13:53:40.635967+0800 threadTest[296:19536] 售票线程A准备上锁
    13:53:40.635773+0800 threadTest[296:19780] 售票线程B准备上锁
    13:53:40.635993+0800 threadTest[296:19536] 售票线程A已上锁
    13:53:40.636022+0800 threadTest[296:19536] 售票线程A卖出去了一张票,还剩下1张票
    13:53:40.636025+0800 threadTest[296:19780] 售票线程B等待中
    13:53:40.636049+0800 threadTest[296:19536] 售票线程A解锁
    13:53:40.636064+0800 threadTest[296:19780] 售票线程B准备上锁
    13:53:40.636075+0800 threadTest[296:19536] 售票线程A准备上锁
    13:53:40.636093+0800 threadTest[296:19780] 售票线程B已上锁
    13:53:40.636102+0800 threadTest[296:19536] 售票线程A等待中
    13:53:40.636126+0800 threadTest[296:19780] 售票线程B卖出去了一张票,还剩下0张票
    13:53:40.636160+0800 threadTest[296:19780] 售票线程B解锁
    13:53:40.636520+0800 threadTest[296:19780] 售票线程B准备上锁
    13:53:40.636548+0800 threadTest[296:19780] 售票线程B已上锁
    13:53:40.636575+0800 threadTest[296:19780] 售票线程B:票已售完
    13:53:40.636600+0800 threadTest[296:19780] 售票线程B解锁
    13:53:40.636129+0800 threadTest[296:19536] 售票线程A准备上锁
    13:53:40.636652+0800 threadTest[296:19536] 售票线程A已上锁
    13:53:40.636678+0800 threadTest[296:19536] 售票线程A:票已售完
    13:53:40.636704+0800 threadTest[296:19536] 售票线程A解锁
    

    2.os_unfair_lock(iOS 10以后,苹果提供用来代替OSSpinLock)

    测试代码和OSSpinLock相同 替换对应上锁解锁代码

    #import <os/lock.h>
    
    // 初始化(os_unfair_lock_t)
    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
    // 加锁
    os_unfair_lock_lock(unfairLock);
    // 解锁
    os_unfair_lock_unlock(unfairLock);
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    os_unfair_lock_trylock(unfairLock)
    //或者
    // 初始化(os_unfair_lock)
    os_unfair_lock unfair_lock = OS_UNFAIR_LOCK_INIT;
    // 加锁
    os_unfair_lock_lock(&unfair_lock);
    // 解锁
    os_unfair_lock_unlock(&unfair_lock);
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    os_unfair_lock_trylock(&unfair_lock);
    

    3.dispatch_semaphore (信号量)

    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)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

    dispatch_semaphore测试代码01 - 简单模拟停车

        dispatch_queue_t queue = dispatch_queue_create("dispatch_semaphore_test2", DISPATCH_QUEUE_CONCURRENT);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);// 停车场车位数
        
        for (int i = 0; i < 4; i ++) {
            [NSThread currentThread].name = [NSString stringWithFormat:@"汽车%d",i+1];
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"%@进入",[NSThread currentThread]);
                int x = arc4random() % 4;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((x+1) * NSEC_PER_SEC)), queue, ^{
                [NSThread currentThread].name = [NSString stringWithFormat:@"汽车%d",i+1];
                NSLog(@"%@停车%d秒后离开",[NSThread currentThread],(x+1));
                dispatch_semaphore_signal(semaphore);
            });
        }
        NSLog(@"----------------------------");
    

    输出结果

    13:17:19.709194+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽车1}进入
    13:17:19.710074+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽车2}进入
    13:17:21.888932+0800 threadTest[930:177395] <NSThread: 0x280d608c0>{number = 3, name = 汽车1}停车2秒后离开
    13:17:21.889121+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽车3}进入
    13:17:24.098562+0800 threadTest[930:177515] <NSThread: 0x280d5b100>{number = 4, name = 汽车2}停车4秒后离开
    13:17:24.099205+0800 threadTest[930:177270] <NSThread: 0x280d0d8c0>{number = 1, name = 汽车4}进入
    13:17:24.099399+0800 threadTest[930:177270] ----------------------------
    13:17:26.289179+0800 threadTest[930:177395] <NSThread: 0x280d608c0>{number = 3, name = 汽车3}停车4秒后离开
    13:17:26.289179+0800 threadTest[930:177515] <NSThread: 0x280d5b100>{number = 4, name = 汽车4}停车2秒后离开
    

    dispatch_semaphore测试代码02
    多个异步请求完成之后再执行最后的任务(用dispatch_group_enter(dispatch_group_t group) 和 dispatch_group_leave(dispatch_group_t group)也可以实现)

        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_queue_create("dispatch_semaphore_test1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"任务01开始%@",[NSThread currentThread]);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"任务01完成%@",[NSThread currentThread]);
                dispatch_semaphore_signal(semaphore);
            });
            NSLog(@"任务01等待完成%@",[NSThread currentThread]);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"任务02开始%@",[NSThread currentThread]);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"任务02完成%@",[NSThread currentThread]);
                dispatch_semaphore_signal(semaphore);
            });
            NSLog(@"任务02等待完成%@",[NSThread currentThread]);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"所有任务已完成");
        });
    

    输出结果

    18:13:02.689950+0800 threadTest[540:66884] 任务01开始<NSThread: 0x28075cec0>{number = 4, name = (null)}
    18:13:02.690544+0800 threadTest[540:66884] 任务01等待完成<NSThread: 0x28075cec0>{number = 4, name = (null)}
    18:13:02.690597+0800 threadTest[540:66944] 任务02开始<NSThread: 0x28075cb40>{number = 3, name = (null)}
    18:13:02.690754+0800 threadTest[540:66944] 任务02等待完成<NSThread: 0x28075cb40>{number = 3, name = (null)}
    18:13:03.737854+0800 threadTest[540:66847] 任务02完成<NSThread: 0x280735a40>{number = 1, name = main}
    18:13:04.837859+0800 threadTest[540:66847] 任务01完成<NSThread: 0x280735a40>{number = 1, name = main}
    18:13:04.838539+0800 threadTest[540:66847] 所有任务已完成
    18:13:11.508223+0800 threadTest[540:66944] 任务01开始<NSThread: 0x28075cb40>{number = 3, name = (null)}
    18:13:11.508920+0800 threadTest[540:66964] 任务02开始<NSThread: 0x28075c800>{number = 5, name = (null)}
    18:13:11.509366+0800 threadTest[540:66944] 任务01等待完成<NSThread: 0x28075cb40>{number = 3, name = (null)}
    18:13:11.509425+0800 threadTest[540:66964] 任务02等待完成<NSThread: 0x28075c800>{number = 5, name = (null)}
    18:13:12.537780+0800 threadTest[540:66847] 任务02完成<NSThread: 0x280735a40>{number = 1, name = main}
    18:13:13.654482+0800 threadTest[540:66847] 任务01完成<NSThread: 0x280735a40>{number = 1, name = main}
    18:13:13.655111+0800 threadTest[540:66847] 所有任务已完成
    

    4.pthread_mutex(互斥锁)

    测试代码和OSSpinLock相同 替换对应上锁解锁代码

    #import <pthread/pthread.h>
    
    // 普通初始化
    pthread_mutex_t mutexLock;
    pthread_mutex_init(&mutexLock, NULL); 
    // 宏初始化
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    // 加锁
    pthread_mutex_lock(&mutexLock);
    // 解锁
    pthread_mutex_unlock(&mutexLock);
    // 尝试加锁,可以加锁时返回的是 0,否则返回一个错误(OSSpinLockTry(&spinLock)返回YES或者NO)
    pthread_mutex_trylock(&mutexLock)
    

    5.pthread_mutex(recursive) (递归锁)

    注: 递归锁可以被同一线程多次请求,而不会引起死锁。即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。这主要是用在循环或递归操作中。
    pthread_mutex_t mutex_t;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr
    pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
    pthread_mutex_init(&mutex_t, &attr);
    pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
    pthread_mutex_lock(&mutex_t);// 加锁
    pthread_mutex_unlock(&mutex_t);// 解锁
    pthread_mutexattr_t设置的相关函数及其说明

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive:2];
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive:2];
    });
    
    -(void)recursive:(int)flag{
        pthread_mutex_lock(&_pLock);
        sleep(1);
        if (flag > 0) {
            NSLog(@"%@请求%d", [NSThread currentThread], flag);
            [self recursive:(--flag)];
        }else{
            flag--;
            NSLog(@"%@请求成功", [NSThread currentThread]);
        }
        pthread_mutex_unlock(&_pLock);
        NSLog(@"递归解锁%d", flag + 1);
    }
    
    // 输出结果
    15:02:17.337545+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}请求2
    15:02:18.343244+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}请求1
    15:02:19.348308+0800 threadTest[1096:242086] <NSThread: 0x281e7a0c0>{number = 3, name = (null)}请求成功
    15:02:19.348655+0800 threadTest[1096:242086] 递归解锁0
    15:02:19.348980+0800 threadTest[1096:242086] 递归解锁1
    15:02:19.349154+0800 threadTest[1096:242086] 递归解锁2
    15:02:20.355346+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}请求2
    15:02:21.356200+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}请求1
    15:02:22.361770+0800 threadTest[1096:242091] <NSThread: 0x281e468c0>{number = 4, name = (null)}请求成功
    15:02:22.361986+0800 threadTest[1096:242091] 递归解锁0
    15:02:22.362130+0800 threadTest[1096:242091] 递归解锁1
    15:02:22.362502+0800 threadTest[1096:242091] 递归解锁2
    

    6.NSLock(互斥锁)

    NSLock 是 Objective-C 以对象的形式暴露给开发者的一种锁,只是在内部封装了一个 pthread_mutex,属性为 PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。NSLock 比 pthread_mutex 略慢的原因在于它需要经过方法调用,同时由于缓存的存在,多次方法调用不会对性能产生太大的影响。

    测试代码和OSSpinLock相同 替换对应上锁解锁代码

    // 初始化
    NSLock *lock = [[NSLock alloc] init];
    [lock lock];// 加锁
    [lock unlock];// 解锁
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    [lock tryLock];
    // 这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
    [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    

    7. NSCondition(条件锁)

    _condition = [[NSCondition alloc] init];// 初始化
    [_condition lock];// 加锁
    [_condition unlock];// 解锁
    [_condition wait];// 进入等待状态
    [_condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];// 让一个线程等待一定的时间
    [_condition signal];// 唤醒一个等待的线程
    [_condition broadcast];// 唤醒所有等待的线程

    使用NSCondition模拟售票,并增加补充车票功能

        BOOL adding;// 是否正在补充车票
        BOOL flag;// 是否还能补充车票
    ----------------------------------------------
        //设置票数
        self.totalCount = 2;
        adding = NO;
        flag = YES;
        // 初始化
        _condition = [[NSCondition alloc] init];
        _condition.name = @"售票";
    
        dispatch_queue_t queue = dispatch_queue_create("saleTicket5", DISPATCH_QUEUE_CONCURRENT);
        //售票线程1
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程A";
            [self saleTicket5];
        });
        NSLog(@"-----------------------");
        //售票线程2
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程B";
            [self saleTicket5];
        });
    
    -(void)saleTicket5{
        while (1) {
            NSLog(@"%@准备上锁",[NSThread currentThread].name);
            [_condition lock];
            NSLog(@"%@已上锁",[NSThread currentThread].name);
            if (self.totalCount > 0) {
                self.totalCount --;
                NSLog(@"%@卖出去了一张票,还剩下%zd张票", [NSThread currentThread].name,self.totalCount);
                [_condition unlock];
                NSLog(@"%@解锁",[NSThread currentThread].name);
            }else{
                NSLog(@"%@:票已售完,等待补充车票",[NSThread currentThread].name);
                if (!adding) {
                    adding = YES;
                    dispatch_async(dispatch_queue_create("addTicket5", DISPATCH_QUEUE_CONCURRENT), ^{
                        [NSThread currentThread].name = @"添加车票线程";
                        [self addTicket5];
                    });
                }
                [_condition wait];// 进入等待状态
                if (self.totalCount > 0) {
                    NSLog(@"%@:获取到新车票",[NSThread currentThread].name);
                    [_condition unlock];
                    NSLog(@"%@解锁",[NSThread currentThread].name);
                }else{
                    NSLog(@"%@:票已售完",[NSThread currentThread].name);
                    [_condition unlock];
                    NSLog(@"%@解锁",[NSThread currentThread].name);
                    break;
                }
            }
        }
    }
    -(void)addTicket5{
        if (self.totalCount <= 0 && flag) {
            self.totalCount += 2;
            NSLog(@"%@已补充车票",[NSThread currentThread].name);
            flag = NO;
        }else{
            self.totalCount = 0;
            NSLog(@"%@无车票补充",[NSThread currentThread].name);
        }
        [_condition broadcast];
        NSLog(@"%@唤醒所有等待售票线程",[NSThread currentThread].name);
        adding = NO;
    }
    

    输出结果

    17:05:08.425699+0800 threadTest[1173:260434] -----------------------
    17:05:08.430148+0800 threadTest[1173:260571] 售票线程A准备上锁
    17:05:08.430309+0800 threadTest[1173:260571] 售票线程A已上锁
    17:05:08.430454+0800 threadTest[1173:260571] 售票线程A卖出去了一张票,还剩下1张票
    17:05:08.430582+0800 threadTest[1173:260571] 售票线程A解锁
    17:05:08.430703+0800 threadTest[1173:260571] 售票线程A准备上锁
    17:05:08.430836+0800 threadTest[1173:260571] 售票线程A已上锁
    17:05:08.430968+0800 threadTest[1173:260571] 售票线程A卖出去了一张票,还剩下0张票
    17:05:08.431088+0800 threadTest[1173:260571] 售票线程A解锁
    17:05:08.431205+0800 threadTest[1173:260571] 售票线程A准备上锁
    17:05:08.431324+0800 threadTest[1173:260571] 售票线程A已上锁
    17:05:08.431361+0800 threadTest[1173:260572] 售票线程B准备上锁
    17:05:08.431445+0800 threadTest[1173:260571] 售票线程A:票已售完,等待补充车票
    17:05:08.432165+0800 threadTest[1173:260572] 售票线程B已上锁
    17:05:08.432285+0800 threadTest[1173:260572] 售票线程B:票已售完,等待补充车票
    17:05:08.432427+0800 threadTest[1173:260535] 添加车票线程已补充车票
    17:05:08.432530+0800 threadTest[1173:260535] 添加车票线程唤醒所有等待售票线程
    17:05:08.432629+0800 threadTest[1173:260571] 售票线程A:获取到新车票
    17:05:08.432724+0800 threadTest[1173:260571] 售票线程A解锁
    17:05:08.432788+0800 threadTest[1173:260571] 售票线程A准备上锁
    17:05:08.432864+0800 threadTest[1173:260571] 售票线程A已上锁
    17:05:08.433021+0800 threadTest[1173:260571] 售票线程A卖出去了一张票,还剩下1张票
    17:05:08.433543+0800 threadTest[1173:260571] 售票线程A解锁
    17:05:08.433577+0800 threadTest[1173:260572] 售票线程B:获取到新车票
    17:05:08.436824+0800 threadTest[1173:260572] 售票线程B解锁
    17:05:08.437085+0800 threadTest[1173:260572] 售票线程B准备上锁
    17:05:08.437181+0800 threadTest[1173:260572] 售票线程B已上锁
    17:05:08.437260+0800 threadTest[1173:260572] 售票线程B卖出去了一张票,还剩下0张票
    17:05:08.437408+0800 threadTest[1173:260572] 售票线程B解锁
    17:05:08.437475+0800 threadTest[1173:260572] 售票线程B准备上锁
    17:05:08.437540+0800 threadTest[1173:260572] 售票线程B已上锁
    17:05:08.437606+0800 threadTest[1173:260572] 售票线程B:票已售完,等待补充车票
    17:05:08.433616+0800 threadTest[1173:260571] 售票线程A准备上锁
    17:05:08.437749+0800 threadTest[1173:260571] 售票线程A已上锁
    17:05:08.437815+0800 threadTest[1173:260571] 售票线程A:票已售完,等待补充车票
    17:05:08.438012+0800 threadTest[1173:260535] 添加车票线程无车票补充
    17:05:08.438456+0800 threadTest[1173:260535] 添加车票线程唤醒所有等待售票线程
    17:05:08.438559+0800 threadTest[1173:260572] 售票线程B:票已售完
    17:05:08.438628+0800 threadTest[1173:260572] 售票线程B解锁
    17:05:08.438722+0800 threadTest[1173:260571] 售票线程A:票已售完
    17:05:08.438787+0800 threadTest[1173:260571] 售票线程A解锁
    

    8. NSRecursiveLock(递归锁)

    NSRecursiveLock也是通过 pthread_mutex_lock 函数来实现,NSRecursiveLock 与 NSLock 的区别在于内部封装的 pthread_mutex_t 对象的类型不同,NSRecursiveLock的类型为 PTHREAD_MUTEX_RECURSIVE。

    测试代码和pthread_mutex(recursive) 相同 替换对应上锁解锁代码

    NSRecursiveLock *_recursiveLock = [[NSRecursiveLock alloc] init];// 初始化
    [_recursiveLock lock];// 加锁
    [_recursiveLock unlock];// 解锁
    [_recursiveLock tryLock];// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    [_recursiveLock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];// 这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
    

    9. NSConditionLock(条件锁)

    NSConditionLock *_conditionLock = [[NSConditionLock alloc] init];// 初始化
    [_conditionLock lock];// 加锁
    [_conditionLock unlock];// 解锁
    [_conditionLock tryLock];// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    -(void)lockWhenCondition:(NSInteger)condition;//条件成立触发锁,会阻塞当前线程
    -(BOOL)tryLockWhenCondition:(NSInteger)condition;//尝试条件成立触发锁
    -(void)unlockWithCondition:(NSInteger)condition;//条件成立解锁
    -(BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内,会阻塞当前线程
    -(BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//触发锁 条件成立 并且在等待时间之内,会阻塞当前线程

        NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
            [lock lockWhenCondition:1];
            NSLog(@"任务01开始%@",[NSThread currentThread]);
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                NSLog(@"任务01完成%@",[NSThread currentThread]);
                [lock unlockWithCondition:3];
            });
            NSLog(@"任务01等待完成%@",[NSThread currentThread]);
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //        if ([lock lockWhenCondition:2]) {
            if ([lock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:6]]) {
                NSLog(@"任务02开始%@",[NSThread currentThread]);
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    NSLog(@"任务02完成%@",[NSThread currentThread]);
                    [lock unlockWithCondition:4];
                });
                NSLog(@"任务02等待完成%@",[NSThread currentThread]);
            }else{
                [lock lockWhenCondition:4];
                NSLog(@"任务02开始%@",[NSThread currentThread]);
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    NSLog(@"任务02完成%@",[NSThread currentThread]);
                    [lock unlockWithCondition:5];
                });
                NSLog(@"任务02等待完成%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //        sleep(3);
            if ([lock tryLockWhenCondition:3]) {
                NSLog(@"任务03开始%@",[NSThread currentThread]);
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    NSLog(@"任务03完成%@",[NSThread currentThread]);
                    [lock unlockWithCondition:2];
                });
                NSLog(@"任务03等待完成%@",[NSThread currentThread]);
            }else{
                [lock lockWhenCondition:3];
                NSLog(@"任务03失败%@",[NSThread currentThread]);
                [lock unlockWithCondition:4];
            }
        });
    

    输出结果

    11:31:32.311166+0800 threadTest[1541:400982] 任务01开始<NSThread: 0x2832308c0>{number = 3, name = (null)}
    11:31:32.311597+0800 threadTest[1541:400982] 任务01等待完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
    11:31:34.497140+0800 threadTest[1541:401005] 任务01完成<NSThread: 0x283239540>{number = 4, name = (null)}
    11:31:34.497599+0800 threadTest[1541:401003] 任务03失败<NSThread: 0x283220200>{number = 5, name = (null)}
    11:31:38.316952+0800 threadTest[1541:400982] 任务02开始<NSThread: 0x2832308c0>{number = 3, name = (null)}
    11:31:38.317327+0800 threadTest[1541:400982] 任务02等待完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
    11:31:39.372796+0800 threadTest[1541:400982] 任务02完成<NSThread: 0x2832308c0>{number = 3, name = (null)}
    

    10. @synchronized(条件锁)

    @synchronized是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读。我们知道 @synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。这是通过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象去哈希值来得到对应的互斥锁。

        //设置票数
        self.totalCount = 10;
        dispatch_queue_t queue = dispatch_queue_create("saleTicket", DISPATCH_QUEUE_CONCURRENT);
        //售票线程1
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程A";
            while (1) {
                sleep(1);
                if (![self saleTicket6]) {
                    break;
                }
            }
        });
        //售票线程2
        dispatch_async(queue, ^{
            [NSThread currentThread].name = @"售票线程B";
            while (1) {
                sleep(2);
                if (![self saleTicket6]) {
                    break;
                }
            }
        });
    
    
    -(BOOL)saleTicket6{
        @synchronized(self) {
            if (self.totalCount > 0) {
                self.totalCount --;
                NSLog(@"%@卖出去了一张票,还剩下%zd张票", [NSThread currentThread].name,self.totalCount);
                return YES;
            }else{
                NSLog(@"%@:票已售完",[NSThread currentThread].name);
                return NO;
            }
        }
        
    }
    

    输出结果

    13:54:39.353828+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下9张票
    13:54:40.354401+0800 threadTest[1604:420478] 售票线程B卖出去了一张票,还剩下8张票
    13:54:40.359244+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下7张票
    13:54:41.363755+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下6张票
    13:54:42.354886+0800 threadTest[1604:420478] 售票线程B卖出去了一张票,还剩下5张票
    13:54:42.366684+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下4张票
    13:54:43.372029+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下3张票
    13:54:44.360261+0800 threadTest[1604:420478] 售票线程B卖出去了一张票,还剩下2张票
    13:54:44.373056+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下1张票
    13:54:45.373976+0800 threadTest[1604:420440] 售票线程A卖出去了一张票,还剩下0张票
    13:54:46.364459+0800 threadTest[1604:420478] 售票线程B:票已售完
    13:54:46.374672+0800 threadTest[1604:420440] 售票线程A:票已售完
    

    关于 @synchronized,这儿比你想知道的还要多

    11. pthread_rwlock(读写锁)

    读写锁是用来解决读者写者问题的,读操作可以共享,写操作是排他的,读可以有多个在读,写只有唯一个在写,同时写的时候不允许读
    对于读数据比修改数据频繁的应用,用读写锁代替互斥锁可以提高效率。因为使用互斥锁时,即使是读出数据(相当于操作临界区资源)都要上互斥锁,而采用读写锁,则可以在任一时刻允许多个读出者存在,提高了更高的并发度,同时在某个写入者修改数据期间保护该数据,以免任何其它读出者或写入者的干扰。

    //初始化attr
    pthread_rwlockattr_t rwlockattr;
    pthread_rwlockattr_init(&rwlockattr);
    //初始化
    pthread_rwlock_t rwlock;
    pthread_rwlock_init(&rwlock, NULL);
    pthread_rwlock_init(&rwlock, &rwlockattr);
    //加读锁 阻塞线程
    pthread_rwlock_rdlock(&rwlock);
    //尝试加读锁 不阻塞线程
    pthread_rwlock_tryrdlock(&rwlock);
    //加写锁 阻塞线程
    pthread_rwlock_wrlock(&rwlock);
    //尝试加写锁 不阻塞线程
    pthread_rwlock_trywrlock(&rwlock);
    //解锁
    pthread_rwlock_unlock(&rwlock);
    //销毁
    pthread_rwlock_destroy(&rwlock);
    pthread_rwlockattr_destroy(&rwlockattr);

        pthread_rwlock_init(&rwlock, NULL);
        _document = @"123";
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [NSThread currentThread].name = @"线程A";
            [self read];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [NSThread currentThread].name = @"线程B";
            [self write];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [NSThread currentThread].name = @"线程C";
            [self read];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [NSThread currentThread].name = @"线程D";
            [self write];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [NSThread currentThread].name = @"线程E";
            [self read];
        });
    
    -(void)read{
        NSLog(@"%@准备查看",[NSThread currentThread].name);
        pthread_rwlock_rdlock(&rwlock);
        NSLog(@"%@查看内容为:%@",[NSThread currentThread].name,_document);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
        NSLog(@"%@结束查看",[NSThread currentThread].name);
    }
    -(void)write{
        NSLog(@"%@准备修改",[NSThread currentThread].name);
        pthread_rwlock_wrlock(&rwlock);
        _document = [NSString stringWithFormat:@"%@修正为321",[NSThread currentThread].name];
        NSLog(@"%@",_document);
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
        NSLog(@"%@完成修改",[NSThread currentThread].name);
    }
    

    输出结果

    14:48:22.750376+0800 threadTest[1623:427484] 线程B准备修改
    14:48:22.750646+0800 threadTest[1623:427484] 线程B修正为321
    14:48:22.750827+0800 threadTest[1623:427485] 线程A准备查看
    14:48:22.751105+0800 threadTest[1623:427487] 线程C准备查看
    14:48:22.751798+0800 threadTest[1623:427486] 线程D准备修改
    14:48:22.752112+0800 threadTest[1623:427498] 线程E准备查看
    14:48:23.755845+0800 threadTest[1623:427484] 线程B完成修改
    14:48:23.755920+0800 threadTest[1623:427485] 线程A查看内容为:线程B修正为321
    14:48:23.755941+0800 threadTest[1623:427487] 线程C查看内容为:线程B修正为321
    14:48:24.760281+0800 threadTest[1623:427485] 线程A结束查看
    14:48:24.760842+0800 threadTest[1623:427487] 线程C结束查看
    14:48:24.761085+0800 threadTest[1623:427486] 线程D修正为321
    14:48:25.765181+0800 threadTest[1623:427486] 线程D完成修改
    14:48:25.765313+0800 threadTest[1623:427498] 线程E查看内容为:线程D修正为321
    14:48:26.766249+0800 threadTest[1623:427498] 线程E结束查看
    

    参考资料

    iOS 开发中的八种锁(Lock)
    深入理解 iOS 开发中的锁
    iOS 十种线程锁

    相关文章

      网友评论

          本文标题:iOS 线程锁的使用

          本文链接:https://www.haomeiwen.com/subject/fqywrqtx.html