活跃性
活跃性是指好的事情最终会发生。例如,如果你代码的目标是确保你能够持续从数组中push和pop对象,问题是这个过程是否能够永远工作。使用锁带来的问题是锁会引起系统中所有线程一直等待 -- 换句话说,就是死锁。如果你能保证你应用的活跃性,那么死锁应该永远都不会发生。
问题
想象你有两个线程:A和B。在Astart之前,A一直等待B结束。但是,在线程B继续运行之前,B一直等待线程A结束。
对于一个实际的例子,你可以在Listing 6-7中查看它的代码。注意push和pop线程被同一对象锁住,即lockedObj。图6-7显示了这两个线程是如何在同一个对象上锁住,并永远的互相等待。
在新的例子中你可以看到,pop线程一直在while循环上等待,因为storages数组的大小为0。同时push线程不能往数组中添加对象,因为代码被对象lockedObj锁住了。push线程现在必须等待pop线程执行完,然后把锁归还给lockedObj对象。因此,这两个线程停止,然后永远的等待对方。这里有一些死锁问题的解决办法。
如果你的代码发生了死锁,你不能再使用@synchronized(lockedObj),因为这样直接使用是不能避免死锁的。
NSLock:你可以使用它来保护并发访问的代码块,就像使用@synchronized(obj)那样,但是当这部分代码lock和unlock时,你能够控制它。
NSCondition:这对于生产者和消费者模式是非常有用的,就像前面显示的push和pop这个例子。
NSLock 解决办法
你可以有两种使用方式,lock或tryLock。使用lock的方法,这个方法不能在获取锁,它会停止,然后等待直到它获取到锁。使用tryLock,如果方法返回NO,意味着锁已经被其他线程占有,调用的线程不能获取它。
它已经测试了,如果获取到锁就继续执行。如果你没有获取到锁,一切正常,线程会继续执行其他没有使用锁的代码。
NSCondition 解决办法
使用NSLock,你会看到通过使用[testLock lock]来获取一个锁;你不能停止或挂起你的线程来等待一些条件。你唯一能做的就是继续执行直到你释放锁,这样其他线程才能获取锁执行。
再看看push,pop这个例子。让线程不停的运行检查数组是否有数据,效率是不高的。在循环中,如果线程发现数组中没有数据,它应该停止然后等待数组有数据了,就把它给取出来。这种方法的好处是你能够挂起你的线程,而不会浪费系统的资源。
为了让一个线程停止,等待,还有同时返回一个锁,你需要使用NSCondition。Listing 6-8 演示了如何使用NSCondition来执行push,pop这个例子。
还有其他的锁机制,如NSRecursiveLock和NSConditionLock,但是使用NSLock和NSCondition在大部分情况下就足够了。对于线程,你应该总是简单明了,因为多线程会在你的代码中引入不确定的bug。
注意:NSRecursiveLock是非常有用的,如果你有一个线程想要多次获取一个锁,而又不会发生死锁的话。NSRecursiveLock依然会阻塞其他线程来访问代码块。 |
死锁
使用锁可能会导致死锁的发生。死锁就像前面介绍的那样,但是更多的情况是,有两个或多个锁时,然后线程之间相互等待。
图6-8演示了线程1获取object1对象锁和线程2获取object2对象锁的解决办法。然后线程1想要获取object2的锁,但是必须等待线程2释放这个锁。同时,线程2想要获取object1的锁,但是必须等待线程1释放这个锁。正如你看到的,两个线程互相等待,没有一个能继续运行。
有一些方法能够解决死锁问题,比如reordering threads;minimize locking;a bigger lock;tryLock;time out for locking。这些机制都不难实现;下面的这些图能够帮助你理解他们是如何工作的。
在图6-9中,最简单的方式就是对线程重排序,然后顺序锁定,这样只有当一个使用lock2的线程结束时,其他线程才能获取锁,然后继续执行。
但是,如果你的方法结构是固定的,有一些代码是属于第三方库,很难重排序锁或修改代码。
接下里的一种方法是只有你确实需要锁住的那部分才尝试锁住。这中方法使的锁住的部分最小,如图6-10.
如果有代码或想把你不需要锁住的代码单独分离出来,这种方式是很容易实现的。注意它会使你的代码变得更复杂。
另外一种方法是,在其他锁上实现另外一个更大的锁。这允许其他线程尝试同时访问同样的锁。例如,在图6-11中,由于一个新的更大的锁,线程2现在必须等待线程1完成,在它能获取其他锁或运行必要的代码之前。在正常的代码中,如果能够从更小的锁中移除,用更大的锁取代,你应该这样做,当它能够减少代码的复杂性时。这种机制通常使用在,当你需要在库(你不能或不想修改库中的代码)中防止死锁时。
你应该尝试使用tryLock。使用tryLock,如果一个线程不能获取到锁,它不会停止和等待。线程能够继续执行线程中的其他代码。这是一个使用tryLock的例子:
我介绍的最后一种方法是使用超时,尽管还有很多其他的方法来防止死锁。在objective-c中,你可以指定线程来等待,直到你能获取到锁或需要等待结束的时间。使用lockBeforeDate方法能帮助你达到这个目的。调用这个方法的线程将会阻塞直到你能获取到锁。如果参数中指定的NSDate实例发生了,但是线程还没有获取到锁,它会继续执行。这在死锁时会有帮助;你的线程能够继续执行,这可能会有一些小的风险,但是不可能发生死锁。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。