参考这个answer,我想知道这是正确的吗?
@synchronized不会使任何代码“线程安全”
当我试图找到支持此声明的任何文档或链接时,没有成功。
任何评论和/或答案都将受到赞赏。
为了更好的线程安全,我们可以使用其他工具,我知道。
答案 0 :(得分:38)
@synchronized
如果使用得当,确实可以使代码线程安全。
例如:
假设我有一个访问非线程安全数据库的类。我不想同时读取和写入数据库,因为这可能会导致崩溃。
所以我想说我有两种方法。 storeData:和一个名为LocalStore的单例类的readData。
- (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
在您正在同步的对象上创建互斥锁。换句话说,如果任何代码想要访问@synchronized(self) { }
块中的代码,则必须在同一块内运行的所有先前代码之后排队。
如果我们要创建不同的localStore对象,@synchronized(self)
将仅单独锁定每个对象。这有意义吗?
这样想。你有一大群人在不同的行中等待,每行编号为1-10。您可以选择希望每个人等待的行(通过基于每行进行同步),或者如果您不使用@synchronized
,则可以直接跳到前面并跳过所有行。第1行中的某个人不必等待第2行中的某个人完成,但第1行中的人确实必须等待他们前面的所有人完成。
答案 1 :(得分:22)
我认为问题的实质是:
是正确使用同步能够解决任何线程安全问题 问题
技术上是,但在实践中,建议学习和使用其他工具。
我会在不假设先前知识的情况下回答。
正确的代码是符合其规范的代码。一个好的规范定义
线程安全代码是由多个线程执行时保持正确的代码。因此,
高级别的要点是:线程安全要求在多线程执行期间规范成立。要实际编写代码,我们必须做一件事:规范对可变共享状态 3 的访问。有三种方法可以做到:
前两个很简单。第三个要求防止以下线程安全问题:
if (counter) counter--;
,其中一个解决方案是@synchronize(self){ if (counter) counter--;}
。为了解决这些问题,我们使用@synchronize
,volatile,内存障碍,原子操作,特定锁,队列和同步器(信号量,障碍)等工具。
回到问题:
正确使用@synchronize能够解决任何线程安全问题 问题
技术上是的,因为上面提到的任何工具都可以使用@synchronize
进行模拟。但这会导致表现不佳并增加与生活相关的问题的机会。相反,您需要为每种情况使用适当的工具。例如:
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读写锁,或创建带锁剥离的集合,或者无论情况需要什么。正确的答案取决于使用模式。无论如何,你应该在你的课程中记录你提供的线程安全保证。
1 即,查看无效状态的对象或违反前/后条件。
2 例如,如果线程A迭代集合X,并且线程B删除了一个元素,则执行崩溃。这是非线程安全的,因为客户端必须在X(synchronize(X)
)的内部锁上进行同步才能拥有独占访问权限。但是,如果迭代器返回集合的副本,则集合将变为线程安全。
3 不可变共享状态或可变非共享对象始终是线程安全的。
答案 2 :(得分:9)
通常,@synchronized
可确保线程安全,但仅在正确使用时才能保证。以递归方式获取锁也是安全的,尽管我在答案中详细说明了here。
有几种常见的方法可以使用@synchronized
错误。这些是最常见的:
使用@synchronized
确保原子对象的创建。
- (NSObject *)foo {
@synchronized(_foo) {
if (!_foo) {
_foo = [[NSObject alloc] init];
}
return _foo;
}
}
因为首次获取锁时_foo
将为nil,所以不会发生锁定,并且多个线程可能会在第一次完成之前创建自己的_foo
。
每次使用@synchronized
锁定新对象。
- (void)foo {
@synchronized([[NSObject alloc] init]) {
[self bar];
}
}
我已经看到了这个代码,以及C#等效lock(new object()) {..}
。由于它每次都试图锁定一个新对象,因此它总是被允许进入代码的关键部分。这不是某种代码魔术。它绝对没有确保线程安全。
最后,锁定self
。
- (void)foo {
@synchronized(self) {
[self bar];
}
}
虽然本身不是问题,但如果您的代码使用任何外部代码或本身就是一个库,则可能是一个问题。虽然在内部将对象称为self
,但它在外部具有变量名称。如果外部代码调用@synchronized(_yourObject) {...}
并且您调用@synchronized(self) {...}
,则可能会发现自己处于死锁状态。最好创建一个内部对象来锁定不会暴露在对象外部的对象。在init函数中添加_lockObject = [[NSObject alloc] init];
既便宜又简单,而且安全。
编辑:
我仍然会被问到有关这篇文章的问题,所以这里有一个例子说明为什么在实践中使用@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);
}
应该明白为什么会发生这种情况。锁定foo
和lock
将在前台VS后台线程上以不同的顺序进行调用。很容易说这是不好的做法,但如果Foo
是一个库,则用户不太可能知道代码包含锁。
答案 3 :(得分:4)
对于多线程程序,通常需要将复杂结构保持在一致状态,并且您希望一次只能访问一个线程。常见的模式是使用互斥锁来保护访问和/或修改结构的关键代码段。
答案 4 :(得分:3)
@synchronized
是thread safe
机制。在此函数内编写的代码片段成为critical section
的一部分,一次只能执行一个线程。
@synchronize
隐式应用锁定,而NSLock
明确应用锁定。
它只能确保线程安全,而不是保证。我的意思是你为你的车雇用专家司机,但它不能保证车不会遇到意外。然而,概率仍然是最轻微的。
GCD
(大中央调度)中的伴侣是dispatch_once
。 dispatch_once与@synchronized
完成相同的工作。
答案 5 :(得分:1)
@synchronized
指令是在Objective-C代码中动态创建互斥锁的便捷方式。
互斥锁的副作用:
线程安全将取决于@synchronized
块的使用情况。