NSLock/NSRecursiveLock/NSConditionLock/@synchronized 详细解释

NSLock/NSRecursiveLock/NSConditionLock/@synchronized

http://blog.sina.com.cn/s/blog_8c87ba3b0101ok8y.html

使用NSLock类
     在Cocoa程序中NSLock中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁。
     除了标准的锁行为,NSLock类还增加了tryLock和lockBeforeDate:方法。方法tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程。相反,它只是返回NO。而lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。
     下面的例子显式了你可以是NSLock对象来协助更新一个可视化显式,它的数据结构被多个线程计算。如果线程没有立即获的锁,它只是简单的继续计算直到它可以获得锁再更新显式。 
     BOOL moreToDo = YES; 
     NSLock *theLock = [[NSLock alloc] init]; 
     ... 
     while (moreToDo) { 
            
            
           if ([theLock tryLock]) { 
                  
                 [theLock unlock]; 
           
     }
4.6.3 使用@synchronized指令
     @synchronized指令是在Objective-C代码中创建一个互斥锁非常方便的方法。@synchronized指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你只需要简单的使用Objective-C对象作为锁的令牌,如下面例子所示: 
     - (void)myMethod:(id)anObj 
     
           @synchronized(anObj) 
           
                 // Everything between the braces is protected by the @synchronized directive. 
           
     }
     创建给@synchronized指令的对象是一个用来区别保护块的唯一标示符。如果你在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给anObj参数,那么每次都将会拥有它的锁,并持续处理,中间不被其他线程阻塞。然而,如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其
他线程将会被阻塞直到第一个线程完成它的临界区。
     作为一种预防措施,@synchronized块隐式的添加一个异常处理例程来保护代码。该处理例程会在异常抛出的时候自动的释放互斥锁。这意味着为了使用@synchronized指令,你必须在你的代码中启用异常处理。了如果你不想让隐式的异常处理例程带来额外的开销,你应该考虑使用锁的类。
4.6.4 使用其他Cocoa锁
     以下个部分描述了使用Cocoa其他类型的锁。
·使用NSRecursiveLock对象
     NSRecursiveLock类定义的锁可以在同一线程多次获得,而不会造成死锁。一个递归锁会跟踪它被多少次成功获得了。每次成功的获得该锁都必须平衡调用锁住和解锁的操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
     正如它名字所言,这种类型的锁通常被用在一个递归函数里面来防止递归造成阻塞线程。你可以类似的在非递归的情况下使用他来调用函数,这些函数的语义要求它们使用锁。以下是一个简单递归函数,它在递归中获取锁。如果你不在该代码里使用NSRecursiveLock对象,当函数被再次调用的时候线程将会出现死锁。                  NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; 
     void MyRecursiveFunction(int value) { 
           [theLock lock]; 
           if (value != 0) { 
                 --value; 
                 MyRecursiveFunction(value); 
           }
           [theLock unlock]; 
     
     MyRecursiveFunction(5);
     注意:因为一个递归锁不会被释放直到所有锁的调用平衡使用了解锁操作,所以你必须仔细权衡是否决定使用锁对性能的潜在影响。长时间持有一个锁将会导致其他线程阻塞直到递归完成。如果你可以重写你的代码来消除递归或消除使用一个递归锁,你可能会获得更好的性能。
·使用NSConditionLock对象
     NSConditionLock对象定义了一个互斥锁,可以使用特定值来锁住和解锁。不要把该类型的锁和条件(参见“条件”部分)混淆了。它的行为和条件有点类似,但是它们的实现非常不同。
     通常,当多线程需要以特定的顺序来执行任务的时候,你可以使用一个NSConditionLock对象,比如当一个线程生产数据,而另外一个线程消费数据。生产者执行时,消费者使用由你程序指定的条件来获取锁(条件本身是一个你定义的整形值)。当生产者完成时,它会解锁该锁并设置锁的条件为合适的整形值来唤醒消费者线程,之后消费线程继续处理数据。
     NSConditionLock的锁住和解锁方法可以任意组合使用。比如,你可以使用unlockWithCondition:和lock消息,或使用lockWhenCondition:和unlock消息。当然,后面的组合可以解锁一个锁但是可能没有释放任何等待某特定条件值的线程。
     下面的例子显示了生产者-消费者问题如何使用条件锁来处理。想象一个应用程序包含一个数据的队列。一个生产者线程把数据添加到队列,而消费者线程从队列中取出数据。生产者不需要等待特定的条件,但是它必须等待锁可用以便它可以安全的把数据添加到队列。 
     id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; 
     while(true) { 
           [condLock lock]; 
           
           [condLock unlockWithCondition:HAS_DATA]; 
     }
     因为初始化条件锁的值为NO_DATA,生产者线程在初始化的时候可以毫无问题的获取该锁。它会添加队列数据,并把条件设置为HAS_DATA。在随后的迭代中,生产者线程可以把到达的数据添加到队列,无论队列是否为空或依然有数据。唯一让它进入阻塞的情况是当一个消费者线程充队列取出数据的时候。
     因为消费者线程必须要有数据来处理,它会使用一个特定的条件来等待队列。当生产者把数据放入队列时,消费者线程被唤醒并获取它的锁。它可以从队列中取出数据,并更新队列的状态。下列代码显示了消费者线程处理循环的基本结构。 
     while (true) { 
           [condLock lockWhenCondition:HAS_DATA]; 
            
           [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)]; 
           // Process the data locally. 
     }
·使用NSDistributedLock对象
     NSDistributedLock类可以被多台主机上的多个应用程序使用来限制对某些共享资源的访问,比如一个文件。锁本身是一个高效的互斥锁,它使用文件系统项目来实现,比如一个文件或目录。对于一个可用的NSDistributedLock对象,锁必须由所有使用它的程序写入。这通常意味着把它放在文件系统,该文件系统可以被所有运行在计算机上面的应用程序访问。
     不像其他类型的锁,NSDistributedLock并没有实现NSLocking协议,所有它没有lock方法。一个lock方法将会阻塞线程的执行,并要求系统以预定的速度轮询锁。以其在你的代码中实现这种约束,NSDistributedLock提供了一个tryLock方法,并让你决定是否轮询。
     因为它使用文件系统来实现,一个NSDistributedLock对象不会被释放除非它的拥有者显式的释放它。如果你的程序在用户一个分布锁的时候崩溃了,其他客户端无法访问该受保护的资源。在这种情况下,你可以使用breadLock方法来打破现存的锁以便你可以获取它。但是通常应该避免打破锁,除非你确定拥有进程已经死亡并不可能再释放该锁。
     和其他类型的锁一样,当你使用NSDistributedLock对象时,你可以通过调用unlock方法来释放它。

`@synchronized` 是 Objective-C 中用于实现线程安全的关键字。它的底层实现是通过互斥锁(mutex)来保证代码块的原子性执行。具体原理如下: 1. 当编译器遇到 `@synchronized` 关键字时,它会将关键字后面的对象作为锁对象,并生成一段互斥锁的代码。 2. 在运行时,当某个线程进入 `@synchronized` 代码块时,它会尝试获取锁对象的互斥锁。如果该锁对象的互斥锁已经被其他线程持有,则当前线程会被阻塞,直到互斥锁被释放。 3. 当某个线程持有锁对象的互斥锁时,它可以执行 `@synchronized` 代码块中的代码。其他线程在等待互斥锁时会被阻塞。 4. 当持有锁对象的线程执行完 `@synchronized` 代码块中的代码后,会释放互斥锁,允许其他线程获取互斥锁并执行对应的代码。 通过使用互斥锁,`@synchronized` 关键字确保了同一时间只有一个线程可以进入被保护的代码块,从而避免了多个线程同时访问共享资源导致的数据竞争和不一致性。 需要注意的是,`@synchronized` 关键字在底层使用的是递归锁(recursive lock),这意味着同一个线程可以多次获取同一个锁对象的互斥锁,而不会造成死锁。 然而,`@synchronized` 关键字在性能上可能不如其他更底层的锁机制(如 NSLock、pthread_mutex_t)高效。因此,在需要高性能的场景下,可以考虑使用其他更适合的锁机制来实现线程安全。 希望以上解答对你有所帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值