Objective-C 线程同步

线程同步

线程同步是为了防止多个线程访问同一资源造成的数据安全问题,所采取的一种措施。

比如,两个人从一个篮子里拿苹果,如果,A 拿完,B 再拿,为同步执行,如果 A 和 B 同时拿,就是异步执行,使用了多线程的手段,如果 A 和 B 同时拿到了同一个苹果,怎么办?这时需要 A 和 B 进行线程同步,比如,谁先看到篮子,对篮子加锁,另一个人等着,拿完之后,再解锁。

1.@synchronized(id anObject)

会自动对参数加锁,保证临界区内的代码线程安全。

@synchronized(self){
    //这段代码对其他 @synchronized(self) 都是互斥的
    //self 指向同一个对象
}

@synchronized,代表这个方法加锁,不管哪一个线程运行到这个方法时,都要检查有没有其他线程正在使用这个方法,如果有,要等待使用这个方法的线程完成之后,再运行,若无,则直接运行。

例:

//主线程中
TestObj *obj = [[TestObj alloc] init];

//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(obj){
        [obj method1];
        sleep(10);
    }
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    @synchronized(obj){
        [obj method2];
    }
});
2.NSLock

NSLock 的执行原理是,当某个线程调用到加锁的方法时,先 lock 方法,对该方法进行加锁,然后运行该方法,运行完成之后,调用 unlock 方法,进行解锁。

NSLock *syncLock = [[NSLock alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

    [syncLock lock];

    [method];

    [syncLock unlock];

});
3.NSRecursiveLock

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

例如:

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    static void (^RecursiveMethod)(int);

    RecursiveMethod = ^(int value) {

        [lock lock];
        if (value > 0) {

            NSLog(@"value = %d", value);
            sleep(2);
            RecursiveMethod(value - 1);
        }
        [lock unlock];
    };

    RecursiveMethod(5);
});

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

在这种情况下,我们就可以使用 NSRecursiveLock。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被 lock 的次数。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。

所以,对上面代码进行修改:

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

NSRecursiveLock 除了实现 NSLocking 协议的方法外,还提供了两个方法,分别如下:

// 在给定的时间之前去尝试请求一个锁
- (BOOL)lockBeforeDate:(NSDate *)limit

// 尝试去请求一个锁,并会立即返回一个布尔值,表示尝试是否成功
- (BOOL)tryLock

这两个方法都可以用于在多线程的情况下,去尝试请求一个递归锁,然后根据返回的布尔值,来做相应的处理。如下代码所示:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    sleep(2);
    BOOL flag = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    if (flag) {
        NSLog(@"lock before date");

        [lock unlock];
    } else {
        NSLog(@"fail to lock before date");
    }
});

在前面的代码基础上,添加了一段代码,增加一个线程来获取递归锁。我们在第二个线程中尝试去获取递归锁,当然这种情况下是会失败的,输出结果如下:

value = 5
value = 4
fail to lock before date
value = 3
value = 2
value = 1
4.NSConditionLock

NSConditionLock 是条件锁,顾名思义,这个锁可以通过设定条件进行加锁和解锁。

[lock lockWhenCondition:A条件];

表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。

[xxx unlockWithCondition:A条件];

表示释放锁,同时把内部的condition设置为A条件。

例1:

//主线程中
NSConditionLock *theLock = [[NSConditionLock alloc] init];

//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i=0;i<=2;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];
});

线程1中,加锁使用了 lock,无需条件,但在解锁使用了一个整形条件,而线程2则需要一把标识为2的钥匙,所以,当线程1循环到最后一次的时候,才打开线程2中的锁。

例2:

id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];

// producer
while(true)
{
    [condLock lock];
    /* Add data to the queue. */
    [condLock unlockWithCondition:HAS_DATA];
}

// consumer
while (true)
{
    [condLock lockWhenCondition:HAS_DATA];
    /* Remove data from the queue. */
    [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];

    // Process the data locally.
}

条件锁的初始状态是NO_DATA,所以生产者线程在这个时候就会获取到锁,生产完成后再把状态设置为HAS_DATA;这时消费者线程发现条件变成HAS_DATA后就可以获取到锁,直到消费结束后再把状态设置成NO_DATA。

5.NSDistributedLock

NSDistributedLock 是跨进程的分布式锁,底层是用文件系统实现的互斥锁。NSDistributedLock没有实现NSLocking协议,所以没有会阻塞线程的lock方法,取而代之的是非阻塞的tryLock方法。NSDistributedLock只有在锁持有者显式地释放后才会被释放,也就是说当持有锁的应用崩溃后,其他应用就不能访问受保护的共享资源了。

例如:

//程序A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];
    [lock breakLock];
    [lock tryLock];
    sleep(10);
    [lock unlock];
    NSLog(@"appA: OK");
});

//程序B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];

    while (![lock tryLock]) {
        NSLog(@"appB: waiting");
        sleep(1);
    }
    [lock unlock];
    NSLog(@"appB: OK");
});

先运行程序A,然后立即运行程序B,根据打印你可以清楚的发现,当程序A刚运行的时候,程序B一直处于等待中,当大概10秒过后,程序B便打印出了appB:OK的输出,以上便实现了两上不同程序之间的互斥。/Users/mac/Desktop/earning__是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在tryLock返回YES时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。


本文参考:

1.http://www.cocoachina.com/ios/20150513/11808.html

2.http://blog.csdn.net/likendsl/article/details/8568961

3.http://www.tanhao.me/pieces/643.html/