Swift线程安全详解-概念,三种锁,死锁,Atomic,synchronized
原创Blog,转载请注明出处
之后的博客绝大部分源码会用Swift来写了。 一些资料
iOS中的线程什么是线程? iOS中与多线程相关的常用的有NSThread,GCD,NSOperationQueue.还有一个不常用,但是理解很重要的NSRunloop。 什么是线程安全?
再看看WIKI中的定义
举个例子NSMutableArray不是线程安全的,那么以下代码就会小概率崩溃 let queue1 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
let queue2 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
dispatch_async(queue1) { () -> Void in
for index in 1...500{
self.emptyArray.addObject(NSNumber(integer: index))
}
}
dispatch_async(queue2) { () -> Void in
for index in 500...1000{
self.emptyArray.addObject(NSNumber(integer: index))
}
}
运行的话,崩溃如图 错误Log
注意,这种崩溃是小概率的,但是当用户量大的时候,就会知道小概率也是不可忽视的 错误原因-抽象到malloc和free的问题 每次malloc会分配新的地址,然后free释放。有可能按照如图的方式malloc-malloc-free-free.这样同一个地址会free两次,也就crash了。类似的在ARC中release一个reference count为0的对象也会出错。 UIKit以及Fundation事实上,大多数Cocoa提供的Api都不是线程安全的,尤其是与UI相关的UIKit,只能在主线程上操作。
那么,为什么不把Cocoa的API写成线程安全的呢?Apple那么多天才工程师,难道解决不了吗?
通常,不可变对象是线程安全的,可变对象不是线程安全的
NSArray
NSAssertionHandler
NSAttributedString
NSCalendarDate
NSCharacterSet
NSConditionLock
NSConnection
NSData
NSDate
NSDecimal functions
NSDecimalNumber
NSDecimalNumberHandler
NSDeserializer
NSDictionary
NSDistantObject
NSDistributedLock
NSDistributedNotificationCenter
NSException
NSFileManager (in OS X v10.5 and later)
NSHost
NSLock
NSLog/NSLogv
NSMethodSignature
NSNotification
NSNotificationCenter
NSNumber
NSObject
NSPortCoder
NSPortMessage
NSPortNameServer
NSProtocolChecker
NSProxy
NSRecursiveLock
NSSet
NSString
NSThread
NSTimer
NSTimeZone
NSUserDefaults
NSValue
NSXMLParser
Object allocation and retain count functions
Zone and memory functions
以下对象不是线程安全的 NSArchiver
NSAutoreleasePool
NSBundle
NSCalendar
NSCoder
NSCountedSet
NSDateFormatter
NSEnumerator
NSFileHandle
NSFormatter
NSHashTable functions
NSInvocation
NSJavaSetup functions
NSMapTable functions
NSMutableArray
NSMutableAttributedString
NSMutableCharacterSet
NSMutableData
NSMutableDictionary
NSMutableSet
NSMutableString
NSNotificationQueue
NSNumberFormatter
NSPipe
NSPort
NSProcessInfo
NSRunLoop
NSScanner
NSSerializer
NSTask
NSUnarchiver
NSUndoManager
objc_sync_enter/objc_sync_exit先看看文档,个人建议,遇到一个新的Api或者技术一定要先看看Apple的文档,别人的博客只是一个参考,很有可能别人博客的理解就是错的,当然也包括我的博客
要成对的使用 objc_sync_enter(object)
//Do something with object
objc_sync_exit(object)
Lock(锁)先看看不加锁会出现什么情况,可以先看看Objc上关于计数器的例子,那个可能更简单更直接一点。 举个例子 class Person{
var name:String
var age:UInt32
init(name:String,age:UInt32){
self.name = name
self.age = age
}
func update(name:String,delay:UInt32,age:UInt32){
self.name = name
self.age = age
}
}
这里,要明白那么,这个update在并行更新Self的时候,就会有问题了。画一张图来详细解释下 于是,我们得到了一个混合的值,这个值是name lucy,age 25,这是一个完全没有任何意义的值。而且, 这种错误,很难调试 class ViewController: UIViewController {
var person = Person(name: "Leo",age: 23)
override func viewDidLoad() {
super.viewDidLoad()
let queue1 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
let queue2 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
dispatch_async(queue1) { () -> Void in
self.person.update("jack",delay: 2,age: 25)
}
dispatch_async(queue2) { () -> Void in
self.person.update("lucy",delay: 1,age: 24)
}
self.performSelector("logPerson",withObject: nil,afterDelay: 4)
// Do any additional setup after loading the view,typically from a nib.
}
func logPerson(){
NSLog("%@ %d",person.name,person.age)
}
}
可以看输出 lucy 25 使用互斥锁-NSLock先看一下这个类的文档 An NSLock object is used to coordinate the operation of multiple threads of execution within the same application. An NSLock object can be used to mediate access to an application’s global data or to protect a critical section of code,allowing it to run atomically.
这个类继承自NSObject,实现了NSLocking协议 使用NSLock的时候,要注意unlock的调用要和lock在同一线程上 当我们调用互斥锁进行加锁以后 class ViewController: UIViewController {
let lock = NSLock()
var person = Person(name: "Leo",age: 23)
override func viewDidLoad() {
super.viewDidLoad()
let queue1 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
let queue2 = dispatch_queue_create("com.test.queue1",DISPATCH_QUEUE_SERIAL)
dispatch_async(queue1) { () -> Void in
self.lock.lock()
self.person.update("queue1",age: 1)
self.lock.unlock()
}
dispatch_async(queue2) { () -> Void in
self.lock.lock()
self.person.update("queue2",age: 2)
self.lock.unlock()
}
self.performSelector("logPerson",afterDelay: 4)
// Do any additional setup after loading the view,typically from a nib.
}
func logPerson(){
NSLog("%@ %d",person.name,person.age)
}
}
输出 Lucy 24 死锁把上述代码修改为 dispatch_async(queue1) { () -> Void in
self.lock.lock()
self.lock.lock()
self.person.update("Jack",age: 25)
self.lock.unlock()
self.lock.unlock()
}
然后,你就会发现死锁了
最后,住线程Log输出 2015-11-26 12:28:27.266 LeoThreadSafe[1365:131140] Leo 23
死锁的原因 当然,死锁也会发生在线程之间
递归锁iOS中,除了NSLock之外,还提供了递归锁,递归锁解决了NSLock在同一个线程上不能多次加锁的问题。 NSRecursiveLock defines a lock that may be acquired multiple times by the same thread without causing a deadlock,a situation where a thread is permanently blocked waiting for itself to relinquish a lock. While the locking thread has one or more locks,all other threads are prevented from accessing the code protected by the lock.
NSRecursiveLock定义了一种锁,这种锁能够在同一个线程上多次加锁,而不会引起死锁(一个线程永远的等待自己解锁)。当被锁住的线程有一个或者多个锁的时候,其他线程就不能访问被保护的代码。
把上文的例子换成递归锁,就能够正常输出了 let lock = NSRecursiveLock()
条件锁Cocoa还提供了另外一种跟高级的锁-NSConditionLock,顾名思义,按照某种条件加锁。 class ViewController: UIViewController {
let lock = NSConditionLock(condition: 10)
var person = Person(name: "Leo",DISPATCH_QUEUE_SERIAL)
dispatch_async(queue1) { () -> Void in
self.lock.lockWhenCondition(10)
self.person.update("Jack",age: 25)
self.lock.unlockWithCondition(10)
}
dispatch_async(queue2) { () -> Void in
self.lock.lockWhenCondition(10)
self.person.update("Lucy",age: 24)
self.lock.unlockWithCondition(10)
}
self.performSelector("logPerson",person.age)
}
}
可重入Wiki的定义
比如这个C函数 int function()
{
mutex_lock();
// ...
function body
// ...
mutex_unlock();
}
如果在body处中断,则改线程一直加锁,不能重入。 关于Objective C参考这篇文章 Atomic一个非Atomic的属性在非ARC的时候像这样 - (void)setUserName:(NSString *)userName { if (userName != _userName) { [userName retain]; [_userName release]; _userName = userName; } }
可以看到,如果在多线程同时set的情况下,可能会造成release两次。程序崩溃。 static inline void reallySetProperty(id self,SEL _cmd,id newValue,ptrdiff_t offset,bool atomic,bool copy,bool mutableCopy)
{
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *slot;
*slot = newValue;
_spin_unlock(slotlock);
}
objc_release(oldValue);
}
可以看到,如果是nonatomic的, synchronized可以看看这个StackOverflow问题 - (NSString *)myString {
@synchronized(self) {
return [[myString retain] autorelease];
}
}
可以类似转变为(实际转变肯能要更复杂,可以这么理解) - (NSString *)myString {
NSString *retval = nil;
pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
pthread_mutex_lock(self_mutex);
retval = [[myString retain] autorelease];
pthread_mutex_unlock(self_mutex);
return retval;
}
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- xml – XSLT:如果标签存在,应用模板;如果没有,请选择静态值
- c# – JWT Web令牌加密 – SecurityAlgoritms.HmacSha256 v
- TDD,Dont DDT
- Oracle数据库
- Yaf入门三之依赖管理工具Composer的使用
- 使用rbenv BUILD FAILED安装ruby-2.0.0-p0
- objective-c – Xcode和WebView:如果没有互联网连接,则加载
- c# – Visual Studio Azure函数发布function.json
- 简析在React Native中如何适配iPhoneX
- 从零开始 React Native(8) flex布局_常用控件案例