iOS几种常用线程锁学习与总结。

开始前,先创建3个线程执行的任务。
- (void)method1 {
    NSLog(@"%@", @"线程1");
}

- (void)method2 {
    NSLog(@"%@", @"线程2");
}

- (void)method3 {
    NSLog(@"%@", @"线程3");
}

1.使用NSLock实现的锁

NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常所使用的,除lock和unlock方法外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),刚并不会阻塞线程,并返回NO。lockBeforeDate:方法会在所指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。

    ///NSLock锁
    NSLock *lock = [[NSLock alloc] init];

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        [self method1];
        sleep(10);
        [lock unlock];
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);//以保证让线程2的代码后执行
        [lock lock];
        [self method2];
        [lock unlock];
    });

执行打印结果:

2018-05-26 09:07:00.966149+0800 AAAA[23178:827090] 线程1

2018-05-26 09:07:10.968050+0800 AAAA[23178:827091] 线程2

通过打印结果可以看到,加锁的线程1在执行完整个任务后,线程2才开始执行。

如果加锁后,锁没有及时释放掉,则会造成死锁现象,当线程1执行之后,其他线程将无法执行。

NSLock锁较为常用,通常是添加在一个线程中,要注意的是添加锁的线程不要是多次执行的,因为添加锁之后,其他线程要等待锁执行之后才能执行,所以添加锁的的代码最好是不耗时的。

2.使用synchronized关键字构建的锁

@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

    UIView *other = [[UIView alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(self){
            [self method1];
            sleep(10);
        }
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized(other){
            [self method2];
        }
    });
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        @synchronized(self){
            [self method3];
        }
    });

执行打印结果:

2018-05-26 09:15:03.896815+0800 AAAA[23309:843305] 线程1

2018-05-26 09:15:04.901968+0800 AAAA[23309:843304] 线程2

2018-05-26 09:15:13.902446+0800 AAAA[23309:843307] 线程3

通过结果可以看出,@synchronized锁对应的为同一个对象时,锁才会生效,当对象为self时,锁的效果执行,线程阻塞,当对象为other时和self的锁不冲突,可以同步执行。相比于NSLock,它的创建更为简介,并且可以执行多条线程的加锁。

3.使用C语言的pthread_mutex_t实现的锁

    __block pthread_mutex_t mutex;
    __block pthread_mutex_t mutex2;
    pthread_mutex_init(&mutex, NULL);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&mutex);
        [self method1];
        sleep(5);
        pthread_mutex_unlock(&mutex);
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&mutex2);
        [self method2];
        pthread_mutex_unlock(&mutex2);
    });
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        pthread_mutex_lock(&mutex);
        [self method3];
        pthread_mutex_unlock(&mutex);
    });

执行打印结果:

2018-05-26 09:31:33.218339+0800 AAAA[23561:869507] 线程1

2018-05-26 09:31:34.219542+0800 AAAA[23561:869506] 线程2

2018-05-26 09:31:38.223177+0800 AAAA[23561:869508] 线程3

pthread_mutex_t定义在pthread.h,所以记得#include pthread_mutex_t锁执行的逻辑与@synchronized类似,需要创建锁的对象,mutex和mutex2为两个锁对象,每个线程只和同一个mutex产生锁的效果。

4.使用GCD来实现的”锁” 

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [self method1];
        sleep(10);
        dispatch_semaphore_signal(semaphore);
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [self method2];
        dispatch_semaphore_signal(semaphore);
    });

GCD锁执行打印的效果与C语言的执行是一样的,书写方法的不同而已。

5.NSRecursiveLock递归锁    

    NSLock *theLock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        static void(^TestMethod)(int);

        TestMethod = ^(int value) {
            [theLock lock];
            if (value > 0)
            {
                [self method1];

                sleep(4);

                TestMethod(value-1);
            }
            [theLock unlock];
        };
        TestMethod(5);
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [theLock lock];
        [self method2];
        [theLock unlock];
    });

执行打印结果:

2018-05-26 09:40:55.891359+0800 AAAA[23673:888329] 线程1

此现象,为死锁现象,为什么会产生这种情况,因为在线程1中,每次循环之后,都重新block回调执行一次线程1的任务,theLock进行了多次的锁lock,自己把自己给锁住了,产生了死锁现象,这里就要引出NSRecursiveLock递归锁了。上面的代码中如果将theLock的类型NSLock替换为NSRecursiveLock递归锁,就不会产生死锁了。

执行打印结果:

2018-05-26 09:44:19.823501+0800 AAAA[23727:894828] 线程1

2018-05-26 09:44:23.829966+0800 AAAA[23727:894828] 线程1

2018-05-26 09:44:27.837278+0800 AAAA[23727:894828] 线程1

2018-05-26 09:44:31.843270+0800 AAAA[23727:894828] 线程1

2018-05-26 09:44:35.849868+0800 AAAA[23727:894828] 线程1

2018-05-26 09:44:39.853977+0800 AAAA[23727:894829] 线程2

NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。

6.NSConditionLock条件锁

在线程中,有时需要对应的条件去启动某一个线程的任务,这时NSLock不一定能满足我们的条件,这个时候需要NSConditionLock条件锁来满足我们。

正如NSConditionLock的命名一样,在加锁的时候我们可以额外添加对应的条件来控制我们的线程开启。

    NSConditionLock *theLock = [[NSConditionLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        for (int i=0;i<=3;i++)
        {
            [theLock lock];

            NSLog(@"thread1:%d",i);

            sleep(2);

            [theLock unlockWithCondition:i];
        }
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [theLock lockWhenCondition:2];

        NSLog(@"thread2");

        [theLock unlock];

    });
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [theLock lockWhenCondition:3];

        NSLog(@"thread3");

        [theLock unlock];

    });

执行打印结果:

2018-05-26 09:53:30.565448+0800 AAAA[23875:912929] thread1:0

2018-05-26 09:53:32.570947+0800 AAAA[23875:912929] thread1:1

2018-05-26 09:53:34.575138+0800 AAAA[23875:912929] thread1:2

2018-05-26 09:53:36.580697+0800 AAAA[23875:912929] thread1:3

2018-05-26 09:53:38.582872+0800 AAAA[23875:912930] thread3

这里有两个地方要注意,1.首先是在线程1中,我们添加了条件锁的入口

[theLock unlockWithCondition:i];

而在线程2线程3中我们同时设置了两个接收条件的锁。i=2和i=3,为什么thread2没有执行,是因为条件锁,在整个执行完之后才会去触发他的条件,执行完整个任务后i=3所以只触发了线程3的任务。那我们如果把线程2的触发条件改成i=3,我们执行的结果为

2018-05-26 09:57:58.681922+0800 AAAA[23989:921838] thread1:0

2018-05-26 09:58:00.687457+0800 AAAA[23989:921838] thread1:1

2018-05-26 09:58:02.689869+0800 AAAA[23989:921838] thread1:2

2018-05-26 09:58:04.694757+0800 AAAA[23989:921838] thread1:3

2018-05-26 09:58:06.700193+0800 AAAA[23989:921836] thread2

2018-05-26 09:58:06.700547+0800 AAAA[23989:921839] thread3

这也就证明了,条件锁对应的锁是1对多的关系,如果用多个线程同时满足了线程1结束时的条件,就会同时执行。


以上是我们常用到的线程锁的使用,以及其对应的一些规则。如果有不完整的地方欢迎留言指教。


你可能感兴趣的:(iOS)