@synchronized是否保证线程安全?

参考这个答案 ,我想知道这是正确的吗?

@synchronized不会使任何代码“线程安全”

正如我试图find任何文件或链接来支持这个声明,没有成功。

任何意见和/或答案将不胜感激。

为了更好的线程安全,我们可以去其他工具,这是我所知道的。

如果使用正确, @synchronized使代码线程安全。

例如:

比方说,我有一个类访问非线程安全的数据库。 我不想同时读写数据库,因为这可能会导致崩溃。

所以我们可以说我有两种方法。 storeData:readData和一个名为LocalStore的单例类。

 - (void)storeData:(NSData *)data { [self writeDataToDisk:data]; } - (NSData *)readData { return [self readDataFromDisk]; } 

现在,如果我将这些方法中的每一个分派到他们自己的线程上,如下所示:

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[LocalStore sharedStore] storeData:data]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[LocalStore sharedStore] readData]; }); 

有可能是我们会崩溃。 但是,如果我们将storeData和readData方法更改为使用@synchronized

  - (void)storeData:(NSData *)data { @synchronized(self) { [self writeDataToDisk:data]; } } - (NSData *)readData { @synchronized(self) { return [self readDataFromDisk]; } } 

现在这个代码将是线程安全的。 需要注意的是,如果我删除了一个@synchronized语句,那么代码将不再是线程安全的。 或者,如果我要同步不同的对象,而不是self

@synchronized在你正在同步的对象上创build一个互斥锁。 所以换句话说,如果任何代码想要访问@synchronized(self) { }块中的代码,它将不得不排在所有先前在同一块内运行的代码之后。

如果我们要创build不同的localStore对象,那么@synchronized(self)将只locking每个对象。 那有意义吗?

像这样想。 你有一大堆人分开等待,每一行都编号为1-10。 你可以select你希望每个人等待的线(通过每行同步),或者如果你不使用@synchronized你可以直接跳到前面,跳过所有的线。 第1行的人不必等待第2行的人完成,但第1行的人必须等待他们前面的每个人完成。

我认为问题的实质是:

是否正确使用同步能够解决任何线程安全的问题?

技术上是的,但在实践中,build议学习和使用其他工具。


我会回答,而不会假设以前的知识。

正确的代码是符合其规范的代码。 一个好的规范定义

  • 不变约束状态,
  • 描述操作效果的先决条件和后置条件。

线程安全的代码是由多个线程执行时保持正确的代码。 从而,

  • 没有任何操作顺序可能违反规范。 1
  • 不变式和条件将在multithreading执行期间保持,而不需要客户端2进行额外的同步。

高层次的关键点是:线程安全要求在multithreading执行期间规范成立。 为了实际编码,我们必须做一件事:规范对可变共享状态的访问3 。 有三种方法可以做到这一点:

  • 防止访问。
  • 使状态不变。
  • 同步访问。

前两个很简单。 第三个需要防止以下线程安全问题:

  • 活跃度
    • 死锁 :两个线程永久阻塞,等待对方释放所需的资源。
    • 活锁 :线程忙于工作,但无法取得任何进展。
    • 饥饿 :一个线程永远被拒绝获得它所需要的资源以取得进展。
  • 安全发布 :发布对象的引用和状态必须同时对其他线程可见。
  • 竞赛条件竞赛条件是输出取决于不可控事件的时间的缺陷。 换句话说,当得到正确的答案依赖于幸运的时机时,竞赛状况就会发生。 任何复合操作都会遇到竞争状态,例如“check-then-act”,“put-if-absent”。 一个例子的问题是, if (counter) counter--; ,其中一个解决scheme是@synchronize(self){ if (counter) counter--;}

为了解决这些问题,我们使用@synchronize ,volatile,内存障碍,primefaces操作,特定锁,队列和同步器(信号量,障碍)等工具。

回到问题:

是否正确使用@synchronize能够解决任何线程安全的问题?

技术上来说,是因为上面提到的任何工具都可以用@synchronize来模拟。 但是这会导致performance不佳,并增加生存相关问题的机会。 相反,您需要针对每种情况使用适当的工具。 例:

 counter++; // wrong, compound operation (fetch,++,set) @synchronize(self){ counter++; } // correct but slow, thread contention OSAtomicIncrement32(&count); // correct and fast, lockless atomic hw op 

在链接问题的情况下,您确实可以使用@synchronize或者GCD读写锁,或者创build一个带有locking剥离的集合,或者任何情况所需的。 正确的答案取决于使用模式。 无论如何,你应该在你的课堂上logging你提供的线程安全保证。


1 也就是说,看到对象处于无效状态或违反前/后条件。

2 例如,如果线程A迭代了一个集合X,而线程B删除了一个元素,则执行会崩溃。 这是非线程安全的,因为客户端必须在X的内部locking( synchronize(X) )上进行synchronize(X)才能具有独占访问权限。 但是,如果迭代器返回集合的副本,则集合将变为线程安全的。

3 不可变共享状态或可变非共享对象始终是线程安全的。

一般来说, @synchronized保证线程安全,但只有在正确使用的情况下。 recursion地获得锁也是安全的,尽pipe在我的答案中有详细的限制。

有几种常见的方法来使用@synchronized错误。 这些是最常见的:

使用@synchronized来确保创buildprimefaces对象。

 - (NSObject *)foo { @synchronized(_foo) { if (!_foo) { _foo = [[NSObject alloc] init]; } return _foo; } } 

由于_foo在第一次获取锁时将为零,所以不会发生locking,并且多个线程可能会在第一次完成之前创build自己的_foo

每次使用@synchronizedlocking一个新的对象。

 - (void)foo { @synchronized([[NSObject alloc] init]) { [self bar]; } } 

我已经看到这个代码相当多,以及C#等效lock(new object()) {..} 。 由于它每次都试图locking一个新的对象,它总是被允许进入代码的关键部分。 这不是某种代码魔法。 它确实没有丝毫的安全性。

最后,lockingself

 - (void)foo { @synchronized(self) { [self bar]; } } 

如果你的代码使用任何外部代码,或者本身就是一个库,它本身并不是一个问题,但这可能是一个问题。 在内部,该对象被称为self ,它在外部具有variables名称。 如果外部代码调用@synchronized(_yourObject) {...}并调用@synchronized(self) {...} ,则可能发现自己处于死锁状态。 最好创build一个内部对象来locking,而不会暴露在对象之外。 添加_lockObject = [[NSObject alloc] init]; 在你的init函数里面是便宜,简单,安全的。

编辑:

我仍然被问到关于这个post的问题,所以这里是一个例子,为什么在实践中使用@synchronized(self)是一个坏主意。

 @interface Foo : NSObject - (void)doSomething; @end @implementation Foo - (void)doSomething { sleep(1); @synchronized(self) { NSLog(@"Critical Section."); } } // Elsewhere in your code dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); Foo *foo = [[Foo alloc] init]; NSObject *lock = [[NSObject alloc] init]; dispatch_async(queue, ^{ for (int i=0; i<100; i++) { @synchronized(lock) { [foo doSomething]; } NSLog(@"Background pass %d complete.", i); } }); for (int i=0; i<100; i++) { @synchronized(foo) { @synchronized(lock) { [foo doSomething]; } } NSLog(@"Foreground pass %d complete.", i); } 

明白为什么会发生这种情况。 lockingfoolock在前台VS后台线程上以不同的顺序调用。 这很容易说这是不好的做法,但是如果Foo是一个库,用户不太可能知道代码包含一个锁。

单独使用@synchronized不会使代码线程安全,但它是编写线程安全代码所使用的工具之一。

对于multithreading程序,通常情况下,您希望以一致的状态维护一个复杂的结构,并且一次只需要一个线程访问。 常见的模式是使用互斥体来保护访问和/或修改结构的代码的关键部分。

@synchronizedthread safe机制。 在这个函数中写的一段代码成为critical section的一部分,一次只能执行一个线程。

@synchronize隐式应用锁,而NSLock明确应用它。

它只保证线程安全,不保证。 我的意思是你为你的汽车雇佣了专家司机,但是并不能保证汽车不会遇到事故。 然而,可能性仍然是丝毫的。

GCD (grand central dispatch)中的伙伴是dispatch_once 。 dispatch_once执行与@synchronized相同的工作。

@synchronized指令是在Objective-C代码中快速创build互斥锁的一种便捷方式。

互斥锁的副作用:

  1. 死锁
  2. 饥饿

线程安全将取决于@synchronized块的使用。