原因是因为self很可能会被外部对象访问,被用作key来生成一锁,类似上述代码中的@synchronized (objectA)。两个公共锁交替使用的场景就容易出现死锁。所以正确的做法是传入一个类内部维护的NSObject对象,而且这个对象是对外不可见的[2]。
因此,不相关的多线程代码,要设置不同的锁,一个锁只管一个临界区。除此之外,还有种常见的错误做法会导致并发效率下降:
- //thread A
- [_lock lock];
- atomicSr = @"am on thread A";
- NSLog(@"%@", atomicStr);
- //do some other tasks which are none of business with atomicStr;
- for (int i = 0; i < 100000; i ++) {
- sleep(5);
- }
- [_lock unlock];
-
- //thread B
- [_lock lock];
- atomicSr = @"am on thread B";
- NSLog(@"%@", atomicStr);
- //do some other tasks which are none of business with atomicStr;
- for (int i = 0; i < 100000; i ++) {
- sleep(5);
- }
- [_lock unlock];
即在临界区内包含了与当前加锁对象无关的任务,实际应用中,需要我们尤其注意临界区内的每一个函数,因为其内部实现可能调用了耗时且无关的任务。
递归锁(Recursive lock)
相比较上述提到的@synchronized(self),下面这种情形引起的死锁更加常见:
- @property (nonatomic,strong) NSLock *lock;
-
- _lock = [[NSLock alloc] init];
-
- - (void)synchronizedAMethod {
- [_lock lock];
- //do some tasks
- [self synchronizedBMethod];
- [_lock unlock];
- }
-
- - (void)synchronizedBMethod {
- [_lock lock];
- //do some tasks
- [_lock unlock];
- }
A方法已获取锁后,再调用B方法,就会触发死锁,B方法在等待A方法执行完成释放锁后才能继续执行,而A方法执行完成的前提是执行完B方法。实际开发中,可能发生死锁的情形往往隐蔽在方法的层层调用中。因此建议在不能确定是否会产生死锁时,最好使用递归锁。更保守一点的做法是不论何时都使用递归锁,因为很难保证以后的代码会不会在同一线程上多次加锁。
递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作,内部通过一个计数器来实现。除了NSRecursiveLock,也可以使用性能更佳的pthread_mutex_lock,初始化时参数设置为PTHREAD_MUTEX_RECURSIVE即可:
- pthread_mutexattr_t attr;
- pthread_mutexattr_init (&attr);
- pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
- pthread_mutex_init (&_lock, &attr);
- pthread_mutexattr_destroy (&attr);
值得注意的是,@synchronized内部使用的也是递归锁:
- // Begin synchronizing on 'obj'.
- // Allocates recursive mutex associated with 'obj' if needed.
- // Returns OBJC_SYNC_SUCCESS once lock is acquired.
- int objc_sync_enter(id obj)
- {
- int result = OBJC_SYNC_SUCCESS;
-
- if (obj) {
- SyncData* data = id2data(obj, ACQUIRE);
- assert(data);
- data->mutex.lock();
- } else {
- // @synchronized(nil) does nothing
- if (DebugNilSync) {
- _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
- }
- objc_sync_nil();
- }
-
- return result;
- }
总结
想写出高效、安全的多线程代码,只是熟悉GCD、@synchronized、NSLock这几个API是不够的,还需要了解更多API背后的知识,深刻理解临界区的概念、理清各个任务之间的时序关系是必要条件。 (编辑:厦门网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|