--参考:(官网) https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html + (中文翻译) http://www.cocoachina.com/bbs/read.php?tid=87592
应用程序里面多个线程的存在引发了潜在安全问题当access to resources from multiple threads of execution。两个线程同时修改同一资源有可能以意想不到的方式互相干扰。比如,一个线程可能覆盖其他线程改动的地方,或让应用程序进入一个未知的潜在无效状态。如果你幸运的话,受损的资源可能会导致明显的性能问题或崩溃,这样比较容易跟踪并修复它。然而如果你不走运,资源受损可能导致微妙的错误,这些错误不会立即显现出来,而是很久之后才出现,或者the errors might require a significant overhaul of your underlying coding assumptions。
但涉及到线程安全时,一个好的设计是最好的保护。避免共享资源,并尽量减少线程间的interactions,这样可以让它们减少互相的干扰。但是一个完全无干扰的设计是不可能的。在线程必须交互的情况下,你需要使用同步工具(synchronization tools),来确保当它们交互的时候是安全的。
Mac OS X 和 iOS提供了你可以使用的多个同步工具,从提供互斥访问你程序的有序的事件的工具等。以下个部分介绍了这些工具和如何在代码中使用他们来安全的访问程序的资源。
为了防止不同线程意外修改数据,你可以设计你的程序没有同步问题,或你也可以使用同步工具。尽管完全避免出现同步问题相对更好一点,但是几乎总是无法实现。 以下个部分介绍了你可以使用的同步工具的基本类别。
原子操作是同步的一个简单的形式,它处理简单的数据类型。原子操作的优势是它们不block competing threads。对于简单的操作,比如递增一个计数器,原子操作比使用锁具有更高的性能优势。
Mac OS X 和 iOS 包含了许多在 32 位和 64 位执行基本的数学和逻辑运算的操作。Among these operations are atomic versions of the compare-and-swap, test-and-set, and test-and-clear operations。查看支持原子操作的列表,参阅/user/include/libkern/OSAtomic.h头文件和参考atomic 主页。
内存屏障(memory barrier)是一个使用来确保内存操作按照正确的顺序工作的 非阻塞的同步工具。内存屏障的作用就像一个栅栏,迫使处理器来完成位于障碍前面 的任何加载和存储操作,才允许它执行位于屏障之后的加载和存储操作。内存屏障同样使用来确保一个线程(但对另外一个线程可见)的内存操作总是按照预定的顺序完成。如果在这些地方缺少内存屏障有可能让其他线程看到看似不可能的结果(详见Memory Barriers的Wikipedia)。为了使用一个内存屏障,你只要在你代码里面需要的地 方简单的调用OSMemoryBarrier 函数。
Volatile 变量适用于另外一种memory constraint 对于individual variables。编译器优化代码通过加 载这些变量的值进入寄存器。对于本地变量,这通常不会有什么问题。但是如果一个变量对另外一个线程可见,那么这种优化可能会阻止其他线程发现变量的任何变化。在变量之前加上关键字 volatile 可以强制编译器每次使用变量的时候都从内存里面 加载。You might declare avariable as volatile if its value could be changed at any time by an external source that the compiler may not be able to detect。
因为内存屏障和 volatile 变量降低了编译器可执行的优化,因此你应该谨慎使用它们,只在有需要的地方时候,以确保正确性。更多使用内存屏障的信息,参阅OSMemoryBarrier 主页
锁是最常用的同步工具。你可以是使用锁来保护临界区(critical section of your code),这些代码段在同一个时间只能允许被一个线程访问。比如,一个临界区可能会操作一个特定的数据结构,或使用了每次只能一个客户端client访问的资源。通过在临界区加锁,你可以排除其它线程对这段代码的影响。
下表列出了程序最常使用的锁。Mac OS X 和 iOS 提供了这些锁里面大部分类型的实现,但是并不是全部实现。对于不支持的锁类型,下面列表说明了为什么这些锁不能直接在平台上面实现的原因。
Mutex[互斥锁] | A mutually exclusive (or mutex) lock acts as a protective barrier around a resource. A mutex is a type ofsemaphorethat grants access to only one thread at a time.If a mutex is in use and another thread tries to acquire it, that thread blocks until the mutex is released by its original holder. If multiple threads compete for the same mutex, only one at a time is allowed access to it. |
Recursive lock[递归锁] | A recursive lock is a variant on the mutex lock. A recursive lock allows a single thread to acquire the lock multiple times before releasing it. Other threads remain blocked until the owner of the lock releases the lock the same number of times it acquired it. Recursive locks are used during recursive iterations primarily but may also be used in cases where multiple methods each need to acquire the lock separately. |
Read-write lock[读写锁] | A read-write lock is also referred to as a shared-exclusive lock. This type of lock is typically used in larger-scale operations and can significantly improve performance if the protected data structure is read frequently and modified only occasionally. During normal operation, multiple readers can access the data structure simultaneously. When a thread wants to write to the structure, though, it blocks until all readers release the lock, at which point it acquires the lock and can update the structure. While a writing thread is waiting for the lock, new reader threads block until the writing thread is finished.The system supports read-write locks using POSIX threads only. For more information on how to use these locks, see thepthread主页 |
Distributed lock [分布锁] | A distributed lock provides mutually exclusive access at the process level. Unlike a true mutex, a distributed lock does not block a process or prevent it from running. It simply reports when the lock is busy and lets the process decide how to proceed. |
Spin lock [自旋锁] | A spin lock polls its lock condition repeatedly until that condition becomes true. Spin locks are most often used on multiprocessor systems where the expected wait time for a lock is small. In these situations, it is often more efficient to poll than to block the thread, which involves a context switch and the updating of thread data structures. The system does not provide any implementations of spin locks because of their polling nature, but you can easily implement them in specific situations. For information on implementing spin locks in the kernel, see Kernel Programming Guide. |
Double-checked lock [双重检查锁] | A double-checked lock is an attempt to reduce the overhead of taking a lock by testing the locking criteria prior to taking the lock. Because double-checked locks are potentially unsafe, the system does not provide explicit support for them and their use is discouraged. |
条件是信号量(semaphore)的另外一个形式,它允许在条件为真的时候线程间互相发送信号。条件通常被使用来说明资源可用性,或用来确保任务以特定的顺序执行。When a thread tests a condition, it blocks unless that condition is already true. It remains blocked until some other thread explicitly changes and signals the condition。条件和互斥锁(mutex lock)的区别在于multiple threads may be permitted access to the condition at the same time。条件像个守门人允许不同线程通过根据一些指定的标准(specified criteria)。
一个你使用条件的方式是manage a pool of pending events。事件队列可能使用condition variable来给等待线程发送信号,此时它们在事件队列中的时候。如果一个事件到达时,队列将给条件发送合适信号。如果一个线程已经处于等待,它会被唤醒,届时它将会取出事件并处理它。如果两个事件到达队列的时间大致相同,队列将会发送两次信号唤醒两个线程。
系统通过几个不同的技术来支持条件。然而正确实现条件需要仔细编写代码。详见”Using Conditions”小节例子
Cocoa程序包含了一个简单方法of delivering messages in a synchronized manner to a single thread。The NSObject class declares methods for performing a selector on one of the application’s active threads。这些方法允许你的线程以异步的方式来传递消息,以确保它们在同一个线程上面执行是同步的。比如,you might use perform selector messages to deliver results from a distributed computation to your application’s main thread or to a designated coordinator thread.Each request to perform a selector is queued on the target thread’s run loop and the requests are then processed sequentially in the order in which they were received.。
关于执行selector的例子和更多关于如何使用它们的信息,参考“Cocoa Perform Selector Sources”小节。
同步帮助确保你代码的正确性,但同时将会牺牲部分性能,甚至在无竞争(uncontested)的情况下。同步工具的使用将在后面介绍。锁和原子操作通常包含了内存屏障(memory barriers)和内核级别(kernel-level)同步的使用来确保代码正确被保护。如果,发生锁的争夺,你的线程有可能进入阻塞, 在体验上会产生更大的迟延。
下表列出了在无竞争(uncontested)情况下使用互斥锁和原子操作的近似的相关成本。这些测试的平均值是使用了上千的样本分析出的结果。随着线程创建时间的推移,互斥的获得(acquisition)时间(即使在无竞争情况下)可能相差也很大,这依赖于进程的加载,计算机的处理 速度和系统和程序现有可用的内存。
图表略(主要是Mutex acquisition time 和 Atomic compare-and-swap大约时间比较)。
当设计你的并发(concurrent)任务时,正确性是最重要的因素,但是也要考虑性能因素。代码在多个线程下面正确执行,但比相同代码在一个单独线程中执行慢,这是难以改善的。如果你是改造已有的单线程应用,你应该给关键任务做基本性能测试。当增加额外线程后,对相同的任务你应该采取新的测量方法并比较多线程和单线程情况下的性能状况。在改变代码之后,线程并没有提高性能,你应该需要重新考虑具体的实现或使用多线程问题。
关于性能的信息和收集指标的工具,参阅 Performance Overview。关于锁和原子操作cost信息,参考”线程成本”小节。
当涉及到多线程应用程序时,没有什么比处理信号量更令人恐惧和困惑的了。信号量是底层BSD机制,它可以用来传递信息给进程或manipulate it in some way。一些应用程序使用信号量来检测特定事件,比如子进程的消亡。系统使用信号量来终止失控进程和communicate other types of information。
使用信号量的问题并不是你要做什么,而是当你程序是多线程的时候它们的行为behavior。在单线程应用程序里面,所有的信号量处理都在主线程进行。在多线程应用程序里面,信号量被delivered to whichever thread happens to be running at the time,而不依赖于特定的硬件错误(比如非法指令)。如果多个线程同时运行,信号量被传递到任何一个系统挑选的线程。换而言之,信号量可以传递给你应用的任何线程。
在你应用程序里面实现信号量处理signal handlers的第一条规则是:avoid assumptions about which thread is handling the signal。如果一个指定的线程想要处理给定的信号,你需要通过某些方法来通知该线程信号何时到达。You cannot just assume that installation of a signal handler from that thread will result in the signal being delivered to the same thread。
关于更多信号量的信息和信号量处理例程的安装信息,参见signal和sigaction主页
同步工具是让你代码安全的有用方法,但是它们并非灵丹妙药。使用太多锁和其他synchronization primitives跟非多线程相比明显会降低你应用的线程性能。在性能和安全之间寻找平衡是一门需要经验的艺术。以下各部分提供帮助你为你应用选择合适的同步级别的技巧。
对于你新的项目,甚至已有项目,设计你的代码和数据结构来避免使用同步是一个很好的解决办法。虽然锁和其他类型同步工具很有用,但是它们会影响任何应用的性能。而且如果整体设计导致特定资源的高竞争,你的线程可能需要等待更长时间。
实现并发concurrency最好的方法是减少你并发任务之间的交互和相互依赖。如果每个任务在它自己的数据集上面操作,那它不需要使用锁来保护这些数据。甚至如果两个任务共享一个普通数据集,你可以查看partitioning that set or providing each task with its own copy。当然,拷贝数据集本身也需要成本,所以在你做出决定前,你需要权衡这些成本和使用同步工具造成的成本那个更可以接受。
同步工具只有当它们被用在应用程序中的所有线程是一致时才是有效的。如果你创建了互斥锁来限制特定资源的访问,你所有线程都必须在试图操纵资源前获得同一互斥锁。如果不这样做导致破坏一个互斥锁提供的保护,这是编程的错误。
------注意Threats to Code Correctness------
当你使用锁和内存屏障locks and memory barriers时,你应该总是小心的把它们放在你代码正确的地方。即使锁看起来在代码中很好放置,但通常会让你产生一个虚假的安全感。以下一系列例子试图通过指出看似无害的代码的漏洞来举例说明该问题。其基本前提是你有一个可变的数组,它包含一组不可变的对象集。假设你想要invoke a method of 数组中第一个对象。你可能会做类似下面那样的代码:
NSLock* arrayLock = GetArrayLock(); NSMutableArray* myArray = GetSharedArray(); id anObject; [arrayLock lock]; anObject = [myArray objectAtIndex:0]; [arrayLock unlock]; [anObject doSomething];因为数组是可变的,所有数组周围的锁防止其他线程修改该数组直到你获得了想要的对象。而且因为对象限制它们本身是不可更改的,所以在调用对象的doSomething方法周围不需要锁。
NSLock* arrayLock = GetArrayLock(); NSMutableArray* myArray = GetSharedArray(); id anObject; [arrayLock lock]; anObject = [myArray objectAtIndex:0]; [anObject doSomething]; [arrayLock unlock];通过把doSomething的调用移到锁的内部,你的代码可以保证该方法被调用的时候该对象还是有效的。不幸的是,如果doSomething方法需要耗费很长的时间,这有可能导致你的代码保持拥有该锁很长时间,这会产生一个性能瓶颈。
该代码的问题is not that the critical region was poorly defined, but that the actual problem was not understood. The real problem is a memory management issue that is triggered only by the presence of other threads。因为它可以被其他线程释放,最好的解决办法是在释放锁之前retain anObject。该解决方案涉及对象被释放,并没有引发一个潜在的性能损失。
NSLock* arrayLock = GetArrayLock(); NSMutableArray* myArray = GetSharedArray(); id anObject; [arrayLock lock]; anObject = [myArray objectAtIndex:0]; [anObject retain]; [arrayLock unlock]; [anObject doSomething]; [anObject release];尽管前面的例子非常简单,它们说明了非常重要的一点。当它涉及到代码正确性时,你需要考虑不仅仅是问题的表面。内存管理和其他影响你设计的因子都有可能因为出现多个线程而受到影响,所以你必须考虑从上到下考虑这些问题。此外,你应该在涉及安全的时候假设编译器总是出现最坏的情况。这种意识和警惕性,可以帮你避免潜在的问题,并确保你的代码运行正确。
任何时候线程试图同时获得多于一个锁,都有可能引发潜在的死锁。当两个不同的线程分别保持一个锁(而该锁是另外一个线程需要的)又试图获得另外线程保持的锁时就会发生死锁。结果是每个线程都会进入持久性阻塞状态,因为它永远不可能获得另外那个锁。
一个活锁和死锁类似,当两个线程竞争同一个资源的时候就可能发生活锁。在发生活锁的情况里,一个线程放弃它的第一个锁并试图获得第二个锁。一旦它获得第二个锁,它返回并试图再次获得第一个锁时。It locks up because it spends all its time releasing one lock and trying to acquire the other lock rather than doing any real work.。
避免死锁和活锁的最好方法是同一个时间只拥有一个锁。如果你必须在同一时间获取多于一个锁,你应该确保其他线程没有做类似的事情。
如果你已经使用了一个互斥锁mutex来保护一个代码段,不要自动假设你需要使用volatile keyword来保护该代码段的重要的变量。一个互斥锁包含了内存屏障memory barrier来确保加载和存储操作是按照正确顺序的。在一个临界区添加关键字volatile到变量上面会强制每次访问该变量的时候都要从内存里面从加载。这两种同步技巧的组合使用在一些特定区域是必须的,但是同样会导致显著的性能损失。如果单独使用互斥锁已经可以保护变量,那么忽略关键字volatile。
为了避免使用互斥锁而不使用volatile变量同样很重要。通常情况下,互斥锁和其他同步机制是比volatile变量更好的方式来保护数据结构的完整性。关键字volatile只是确保从内存加载变量而不是使用寄存器register里面的变量。它不保证你variable is accessed是正确的。
Nonblocking synchronization方式是用来执行某些类型的操作而避免扩展使用锁。锁是synchronize two threads的很好方式,但获取一个锁是一个很昂贵的操作,即使在无竞争的状态下。相比,许多原子操作花费很少的时间来完成操作也可以达到和锁一样的效果。
原子操作可以让你在32位或64位的处理器上面执行简单的数学和逻辑的运算操作。这些操作依赖于特定的硬件设施(和可选的内存屏障)来保证给定的操作在影响内存再次访问的时候已经完成。在多线程情况下,你应该总是使用原子操作,它和内存屏障组合使用来保证多个线程间正确的同步内存。
下表列出了可用的原子运算和本地操作和相应的函数名。这些函数声明在/usr/include/libkern/OSAtomic.h头文件里面,在那里你也可以找到完整的语法syntax。这些函数的64-位版本只能在64位的进程里面使用:
Operation | Function name | Description |
Logical OR(AND、XOR类似) | OSAtomicOr32 OSAtomicOr32Barrier | Performs a logical OR between the specified 32-bit value and a 32-bit mask. |
Compare and swap | OSAtomicCompareAndSwap32 OSAtomicCompareAnd- Swap32Barrier OSAtomicCompareAndSwap64 OSAtomicCompareAnd- Swap64Barrier OSAtomicCompareAndSwapPtr OSAtomicCompareAnd- SwapPtrBarrier OSAtomicCompareAndSwapInt等 |
Compares a variable against the specified old value. If the two values are equal, this function assigns the specified new value to the variable; otherwise, it does nothing. The comparison and assignment are done as one atomic operation and the function returns a Boolean value indicating whether the swap actually occurred |
Test and set | OSAtomicTestAndSet OSAtomicTestAndSetBarrier |
Tests a bit in the specified variable, sets that bit to 1, and returns the value of the old bit as a Boolean value. Bits are tested according to the formula (0x80>>(n&7))of byte ((char*)address + (n >> 3)) where n is the bit number and address is a pointer to the variable. This formula effectively breaks up the variable into 8-bit sized chunks and orders the bits in each chunk in reverse. For example, to test the lowest-order bit (bit 0) of a 32-bit integer, you would actually specify 7 for the bit number; similarly, to test the highest order bit (bit 32), you would specify 24 for the bit number. |
Test and clear | OSAtomicTestAndClear OSAtomicTestAnd- ClearBarrier |
Tests a bit in the specified variable, sets that bit to 0, and returns the value of the old bit as a Boolean value. Bits are tested according to the formula (0x80>>(n&7))of byte ((char*)address + (n >> 3)) where n is the bit number and address is a pointer to the variable. This formula effectively breaks up the variable into 8-bit sized chunks and orders the bits in each chunk in reverse. For example, to test the lowest-order bit (bit 0) of a 32-bit integer, you would actually specify 7 for the bit number; similarly, to test the highest order bit (bit 32), you would specify 24 for the bit number. |
int32_t theValue = 0; OSAtomicTestAndSet(0, &theValue); // theValue is now 128. theValue = 0; OSAtomicTestAndSet(7, &theValue); // theValue is now 1. theValue = 0; OSAtomicTestAndSet(15, &theValue) // theValue is now 256. OSAtomicCompareAndSwap32(256, 512, &theValue); // theValue is now 512. OSAtomicCompareAndSwap32(256, 1024, &theValue); // theValue is still 512.关于原子操作的更多信息,参见 atomic的主页和 /usr/include/libkern/OSAtomic.h头文件。
锁是线程编程同步工具的基础。锁可以让你很容易保护代码中一大块区域以便你可以确保代码的正确性。Mac OS X和iOS都位所有类型的应用程序提供了basic mutex locks(互斥锁),而Foundation框架定义一些特殊情况下互斥锁的additional variants。以下个部分显式了如何使用这些锁的类型。
POSIX互斥锁在很多程序里面很容易使用。为了新建一个互斥锁,你声明并初始化一个pthread_mutex_t的结构。为了锁住和解锁一个互斥锁,你可以使用pthread_mutex_lock和pthread_mutex_unlock函数。下面代码显式了要初始化并使用一个POSIX线程的互斥锁的基础代码。当你用完一个锁之后,只要简单的调用pthread_mutex_destroy来释放该锁的数据结构。
pthread_mutex_t mutex; void MyInitFunction() { pthread_mutex_init(&mutex, NULL); } void MyLockingFunction() { pthread_mutex_lock(&mutex); // Do work. pthread_mutex_unlock(&mutex); }注意:上面的代码只是简单的显式了使用一个 POSIX 线程互斥锁的步骤。你自己的代码应该检查 这些函数返回的错误码,并适当的处理它们。
在 Cocoa 程序中 NSLock 中实现了一个简单的互斥锁。所有锁(包括 NSLock)的 接口实际上都是通过 NSLocking 协议定义的,它定义了 lock 和 unlock 方法。你使用 这些方法来获取和释放该锁。
除了标准的锁行为,NSLock 类还增加了 tryLock 和 lockBeforeDate:方法。方法 tryLock 试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程。相反,它只 是返回 NO。而 lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间 内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回 NO)。
 下面的例子显式了你可以是 NSLock 对象来协助更新一个可视化显式,它的数据 结构被多个线程计算。如果线程没有立即获的锁,它只是简单的继续计算直到它可以 获得锁再更新显式。
BOOL moreToDo = YES; NSLock *theLock = [[NSLock alloc] init]; ... while (moreToDo) { /* Do another increment of calculation */ /* until there’s no more to do. */ if ([theLock tryLock]) { /* Update display used by all threads. */ [theLock unlock]; } }
@synchronized 指令是在 Objective-C 代码中创建一个互斥锁非常方便的方法。 @synchronized 指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取 同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你 只需要简单的使用 Objective-C 对象作为锁的令牌,如下面例子所示:
- (void)myMethod:(id)anObj { @synchronized(anObj) { // Everything between the braces is protected by the @synchronized directive. } }传给@synchronized 指令的对象是一个用来区别保护块的唯一标示符。如果你在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给 anObj 参数,那么每次都将会拥有它的锁,并持续处理,中间不被其他线程阻塞。然而,如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其他线程将会被阻塞直到第一个线程完成它的临界区critical section。
作为一种预防措施,@synchronized 块隐式的添加一个exception handle来保护代码。该handler会在异常抛出的时候自动的释放互斥锁。这意味着为了使用@synchronized 指令,你必须在你的代码中启用异常处理。了如果你不想让隐式的异常处理例程带来额外的开销,你应该考虑使用the lock classes。
关于更多@synchronized 指令的信息,参阅The Objective-C Programming Language
以下个部分描述了使用 Cocoa 其他类型的锁。
------使用 NSRecursiveLock对象
NSRecursiveLock 类定义的锁可以在同一线程多次获得,而不会造成死锁。一个递归(recursive)锁会跟踪它被多少次成功获得了。每次成功的获得该锁都必须有对应的解锁的操作来平衡。只有所有的锁和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
正如它名字所言,这种类型的锁通常被用在一个递归函数里面来防止递归造成阻塞线程。你可以类似的在非递归的情况下使用他来调用函数,这些函数的语义要求它们使用锁。以下是一个简单递归函数,它在递归中获取锁。如果你不在该代码里使用 NSRecursiveLock 对象,当函数被再次调用的时候线程将会出现死锁:
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; void MyRecursiveFunction(int value) { [theLock lock]; if (value != 0) { --value; MyRecursiveFunction(value); } [theLock unlock]; } MyRecursiveFunction(5);注意:因为一个递归锁不会被释放直到所有锁的调用对应的解锁操作,所以你必须仔细权衡 是否决定使用锁对性能的潜在影响。长时间持有一个锁将会导致其他线程阻塞直到递归完成。如 果你可以重写你的代码来消除递归或消除使用一个递归锁,你可能会获得更好的性能。
------使用 NSConditionLock 对象
NSConditionLock 对象定义了一个互斥锁,可以使用特定值来锁住和解锁。不要把该类型的锁和条件(参见“Conditions”小节)混淆了。它的行为和条件有点类似,但是 它们的实现非常不同。
通常,当多线程需要以特定的顺序来执行任务的时候,你可以使用一个 NSConditionLock 对象,比如当一个线程生产数据,而另外一个线程消费数据。生产 者执行时,消费者使用由你程序指定的条件来获取锁(条件本身是一个你定义的整形值)。当生产者完成时,它会解锁该锁并设置锁的条件为合适的整形值来唤醒消费者 线程,之后消费线程继续处理数据。
NSConditionLock 的锁住和解锁方法可以任意组合使用。比如,你可以使用 unlockWithCondition:和 lock 消息,或使用 lockWhenCondition:和 unlock 消息。 当然,后面的组合可以解锁一个锁但是可能没有释放任何等待某特定条件值的线程。
下面的例子显示了生产者-消费者问题如何使用条件锁来处理。想象一个应用程 序包含一个数据的队列。一个生产者线程把数据添加到队列,而消费者线程从队列中 取出数据。生产者不需要等待特定的条件,但是它必须等待锁可用以便它可以安全的 把数据添加到队列。
id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; while(true) { [condLock lock]; /* Add data to the queue. */ [condLock unlockWithCondition:HAS_DATA]; }因为初始化条件锁的值为 NO_DATA,生产者线程在初始化的时候可以毫无问题的 获取该锁。它会添加队列数据,并把条件设置为 HAS_DATA。在随后的迭代中,生产 者线程可以把到达的数据添加到队列,无论队列是否为空或依然有数据。 唯一让它进入阻塞的情况是当一个消费者线程extracting data from the queue。
while (true) { [condLock lockWhenCondition:HAS_DATA]; /* Remove data from the queue. */ [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)]; // Process the data locally. }------使用 NSDistributedLock 对象
NSDistributedLock 类可以被多台主机上的多个应用程序使用来限制对某些共享 资源的访问,比如一个文件。该锁是一个高效的互斥锁,is implemented using a file-system item, such as a file or directory。对于一个可用的NSDistributedLock 对象,锁必须由所有使用它的程序写入。这通常意味着把它放在文件系统,该文件系统可以被所有运行在计算机上面的应用程序访问。
不像其他类型的锁,NSDistributedLock 并没有实现 NSLocking 协议,所有它没 有 lock 方法。一个 lock 方法将会阻塞线程的执行,并要求系统以预定的速度轮询锁。 以其在你的代码中实现这种约束,NSDistributedLock 提供了一个 tryLock 方法,并 让你决定是否轮询poll。
因为它使用文件系统来实现,一个 NSDistributedLock 对象不会被释放除非它的拥有者显式的释放它。如果你的程序在holding a distributed lock时候崩溃了,其他客户端简无法访问该受保护的资源。在这种情况下,你可以使用 breadLock 方法来打破现存的锁以便你可以获取它。但是通常应该避免打破锁,除非你确定拥有进程已经死亡并不 可能再释放该锁。
和其他类型的锁一样,当你使用 NSDistributedLock 对象时,你可以通过调用 unlock 方法来释放它。
[cocoaCondition lock]; while (timeToDoWork <= 0) [cocoaCondition wait]; timeToDoWork--; // Do real work here. [cocoaCondition unlock];下面代码显示了用于给 Cocoa 条件发送信号的代码,并递增他断言变量。你应该 在给它发送信号前锁住条件
[cocoaCondition lock]; timeToDoWork++; [cocoaCondition signal]; [cocoaCondition unlock];
pthread_mutex_t mutex; pthread_cond_t condition; Boolean ready_to_go = true; void MyCondInitFunction() { pthread_mutex_init(&mutex); pthread_cond_init(&condition, NULL); } void MyWaitOnConditionFunction() { // Lock the mutex. pthread_mutex_lock(&mutex); // If the predicate is already set, then the while loop is bypassed; // otherwise, the thread sleeps until the predicate is set. while(ready_to_go == false) { pthread_cond_wait(&condition, &mutex); } // Do work. (The mutex should stay locked.) // Reset the predicate and release the mutex. ready_to_go = false; pthread_mutex_unlock(&mutex); }signaling thread负责 setting the predicate and for sending the signal to the condition lock.。下面代码显示了实现该行为的代码。 在该例子中, the condition is signaled inside of the mutex to prevent race conditions from occurring between the threads waiting on the condition。
void SignalThreadUsingCondition() { // At this point, there should be work for the other thread to do. pthread_mutex_lock(&mutex); ready_to_go = true; // Signal the other thread to begin work. pthread_cond_signal(&condition); pthread_mutex_unlock(&mutex); }注意:上述代码是显示使用 POSIX 线程条件函数的简单例子。你自己的代码应该检测这些函数返 回错误码并恰当的处理它们。