参考这个答案,我想知道这是正确的吗?
@synchronized 不会使任何代码“线程安全”
当我试图找到任何文档或链接来支持这个声明时,没有成功。
对此的任何评论和/或答案将不胜感激。
为了更好的线程安全,我们可以使用其他工具,这是我所知道的。
参考这个答案,我想知道这是正确的吗?
@synchronized 不会使任何代码“线程安全”
当我试图找到任何文档或链接来支持这个声明时,没有成功。
对此的任何评论和/或答案将不胜感激。
为了更好的线程安全,我们可以使用其他工具,这是我所知道的。
@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
在您正在同步的对象上创建互斥锁。因此,换句话说,如果任何代码想要访问@synchronized(self) { }
块中的代码,它就必须排在同一块中运行的所有先前代码的后面。
如果我们要创建不同的 localStore 对象,则@synchronized(self)
只会单独锁定每个对象。那有意义吗?
像这样想。您有一大群人在不同的队伍中等待,每条线路的编号为 1-10。您可以选择希望每个人在哪一行等待(通过按每行同步),或者如果您不使用@synchronized
,您可以直接跳到前面并跳过所有行。第 1 行的人不必等待第 2 行的人完成,但第 1 行的人必须等待他们前面的每个人完成。
我认为问题的本质是:
正确使用同步是否能够解决任何线程安全问题?
从技术上讲是的,但在实践中,建议学习和使用其他工具。
我会在不假设以前的知识的情况下回答。
正确的代码是符合其规范的代码。一个好的规范定义
线程安全代码是在由多个线程执行时保持正确的代码。因此,
高级别的要点是:线程安全要求规范在多线程执行期间成立。要实际编写代码,我们只需要做一件事:规范对可变共享状态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 不可变共享状态或可变非共享对象始终是线程安全的。
通常,@synchronized
保证线程安全,但仅在正确使用时。递归获取锁也是安全的,尽管我在此处的回答中详细说明了一些限制。
有几种常见的使用@synchronized
错误的方法。这些是最常见的:
用于@synchronized
确保创建原子对象。
- (NSObject *)foo {
@synchronized(_foo) {
if (!_foo) {
_foo = [[NSObject alloc] init];
}
return _foo;
}
}
因为在第一次获得锁时将为 nil,所以不会发生锁定,并且多个线程可能会在第一次完成之前_foo
创建自己的。_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
是库,用户不太可能知道代码包含锁。
@synchronized 单独不会使代码线程安全,但它是编写线程安全代码时使用的工具之一。
对于多线程程序,通常需要将复杂结构保持在一致的状态,并且一次只希望一个线程具有访问权限。常见的模式是使用互斥锁来保护访问和/或修改结构的代码的关键部分。
@synchronized
是thread safe
机制。在此函数中编写的一段代码成为 的一部分critical section
,一次只能执行一个线程。
@synchronize
隐式应用锁而NSLock
显式应用它。
它只保证线程安全,而不是保证。我的意思是您为您的汽车聘请了一位专业的司机,但这并不能保证汽车不会发生事故。然而,概率仍然是微乎其微的。
GCD
它在(大中央调度)中的同伴是dispatch_once
。dispatch_once 与 to 做同样的工作@synchronized
。
该@synchronized
指令是在 Objective-C 代码中动态创建互斥锁的便捷方式。
互斥锁的副作用:
线程安全将取决于@synchronized
块的使用。