ios @synchronized 是否保证线程安全?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15392726/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does @synchronized guarantees for thread safety or not?
提问by Anoop Vaidya
With reference to this answer, I am wondering is this correct?
参考这个答案,我想知道这是正确的吗?
@synchronized does not make any code "thread-safe"
@synchronized 不会使任何代码“线程安全”
As I tried to find any documentation or link to support this statement, for no success.
因为我试图找到任何文档或链接来支持这个声明,但没有成功。
Any comments and/or answers will be appreciated on this.
对此的任何评论和/或答案将不胜感激。
For better thread safety we can go for other tools, this is known to me.
为了更好的线程安全,我们可以使用其他工具,这是我知道的。
回答by Hyman Freeman
@synchronized
does make code thread safe if it is used properly.
@synchronized
如果使用得当,确实可以使代码线程安全。
For example:
例如:
Lets say I have a class that accesses a non thread safe database. I don't want to read and write to the database at the same time as this will likely result in a crash.
假设我有一个访问非线程安全数据库的类。我不想同时读取和写入数据库,因为这可能会导致崩溃。
So lets say I have two methods. storeData: and readData on a singleton class called LocalStore.
所以可以说我有两种方法。storeData: 和 readData 在一个名为 LocalStore 的单例类上。
- (void)storeData:(NSData *)data
{
[self writeDataToDisk:data];
}
- (NSData *)readData
{
return [self readDataFromDisk];
}
Now If I were to dispatch each of these methods onto their own thread like so:
现在,如果我将这些方法中的每一个分派到它们自己的线程上,如下所示:
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];
});
Chances are we would get a crash. However if we change our storeData and readData methods to use @synchronized
我们很可能会崩溃。但是,如果我们更改 storeData 和 readData 方法以使用@synchronized
- (void)storeData:(NSData *)data
{
@synchronized(self) {
[self writeDataToDisk:data];
}
}
- (NSData *)readData
{
@synchronized(self) {
return [self readDataFromDisk];
}
}
Now this code would be thread safe. It is important to note that if I remove one of the @synchronized
statements however the code would no longer be thread safe. Or if I were to synchronize different objects instead of self
.
现在这段代码将是线程安全的。重要的是要注意,如果我删除其中一个@synchronized
语句,代码将不再是线程安全的。或者,如果我要同步不同的对象而不是self
.
@synchronized
creates a mutex lock on the object you are syncrhonizing. So in other words if any code wants to access code in a @synchronized(self) { }
block it will have to get in line behind all previous code running within in that same block.
@synchronized
在要同步的对象上创建互斥锁。因此,换句话说,如果任何代码想要访问@synchronized(self) { }
块中的代码,它必须排在同一块中运行的所有先前代码之后。
If we were to create different localStore objects, the @synchronized(self)
would only lock down each object individually. Does that make sense?
如果我们要创建不同的 localStore 对象,则@synchronized(self)
只会单独锁定每个对象。那有意义吗?
Think of it like this. You have a whole bunch of people waiting in separate lines, each line is numbered 1-10. You can choose what line you want each person to wait in (by synchronizing on a per line basis), or if you don't use @synchronized
you can jump straight to the front and skip all the lines. A person in line 1 doesn't have to wait for a person in line 2 to finish, but the person in line 1 does have to wait for everyone in front of them in their line to finish.
像这样想。你有一大群人在不同的线等待,每条线编号为 1-10。您可以选择您希望每个人在哪一行等待(通过在每条线路的基础上同步),或者如果您不使用,@synchronized
您可以直接跳到前面并跳过所有线路。第 1 行的人不必等待第 2 行的人说完,但第 1 行的人确实必须等待他们排在他们前面的每个人都说完。
回答by Jano
I think the essence of the question is:
我认为问题的本质是:
is the proper use of synchronize able to solve any thread-safe problem?
正确使用同步是否能够解决任何线程安全问题?
Technically yes, but in practice it's advisable to learn and use other tools.
从技术上讲是的,但在实践中建议学习和使用其他工具。
I'll answer without assuming previous knowledge.
我会在不假设以前的知识的情况下回答。
Correct codeis code that conforms to its specification. A good specification defines
正确的代码是符合其规范的代码。一个好的规范定义
- invariants constraining the state,
- preconditions and postconditions describing the effects of the operations.
- 约束状态的不变量,
- 描述操作效果的前置条件和后置条件。
Thread-safe codeis code that remains correct when executed by multiple threads. Thus,
线程安全代码是在由多个线程执行时保持正确的代码。因此,
- No sequence of operations can violate the specification.1
- Invariants and conditions will hold during multithread execution without requiring additional synchronization by the client2.
- 任何操作序列都不能违反规范。1
- 不变量和条件将在多线程执行期间保持不变,而无需客户端2进行额外同步。
The high level takeaway point is: thread-safe requires that the specification holds true during multithread execution. To actually code this, we have to do just one thing: regulate the access to mutable shared state3. And there are three ways to do it:
高级要点是:线程安全要求规范在多线程执行期间成立。为了实际编码,我们只需要做一件事:调节对可变共享状态3的访问。并且有三种方法可以做到:
- Prevent the access.
- Make the state immutable.
- Synchronize the access.
- 阻止访问。
- 使状态不可变。
- 同步访问。
The first two are simple. The third one requires preventing the following thread-safety problems:
前两个很简单。第三个要求防止以下线程安全问题:
- liveness
- deadlock: two threads block permanently waiting for each other to release a needed resource.
- livelock: a thread is busy working but it's unable to make any progress.
- starvation: a thread is perpetually denied access to resources it needs in order to make progress.
- safe publication: both the reference and the state of the published object must be made visible to other threads at the same time.
- race conditionsA race condition is a defect where the output is dependent on the timing of uncontrollable events. In other words, a race condition happens when getting the right answer relies on lucky timing. Any compound operation can suffer a race condition, example: “check-then-act”, “put-if-absent”. An example problem would be
if (counter) counter--;
, and one of several solutions would be@synchronize(self){ if (counter) counter--;}
.
- 活力
- 死锁:两个线程永久阻塞,等待对方释放所需的资源。
- livelock:一个线程正忙于工作,但无法取得任何进展。
- 饥饿:一个线程永远被拒绝访问它需要的资源以取得进展。
- 安全发布:发布对象的引用和状态必须同时对其他线程可见。
- 竞争条件竞争条件是一种缺陷,其中输出取决于不可控事件的时间。换句话说,当获得正确的答案依赖于幸运的时机时,就会发生竞争条件。任何复合操作都可能遇到竞争条件,例如:“check-then-act”、“put-if-absent”。一个示例问题是
if (counter) counter--;
,几个解决方案之一是@synchronize(self){ if (counter) counter--;}
。
To solve these problems we use tools like @synchronize
, volatile, memory barriers, atomic operations, specific locks, queues, and synchronizers (semaphores, barriers).
为了解决这些问题,我们使用诸如@synchronize
、易失性、内存屏障、原子操作、特定锁、队列和同步器(信号量、屏障)等工具。
And going back to the question:
回到问题:
is the proper use of @synchronize able to solve any thread-safe problem?
正确使用@synchronize 是否能够解决任何线程安全问题?
Technically yes, because any tool mentioned above can be emulated with @synchronize
. But it would result in poor performance and increase the chance of liveness related problems. Instead, you need to use the appropriate tool for each situation. Example:
从技术上讲是的,因为上面提到的任何工具都可以用@synchronize
. 但这会导致性能不佳并增加与活跃度相关的问题的机会。相反,您需要针对每种情况使用适当的工具。例子:
counter++; // wrong, compound operation (fetch,++,set)
@synchronize(self){ counter++; } // correct but slow, thread contention
OSAtomicIncrement32(&count); // correct and fast, lockless atomic hw op
In the case of the linked question you could indeed use @synchronize
, or a GCD read-write lock, or create a collection with lock stripping, or whatever the situation calls for. The right answer depend on the usage pattern. Any way you do it, you should document in your class what thread-safe guarantees are you offering.
在链接问题的情况下,您确实可以使用@synchronize
,或 GCD 读写锁,或创建带有锁剥离的集合,或任何情况需要。正确答案取决于使用模式。不管你怎么做,你都应该在你的班级中记录你提供的线程安全保证。
1That is, see the object on an invalid state or violate the pre/post conditions.
1即,查看处于无效状态或违反前置/后置条件的对象。
2For example, if thread A iterates a collection X, and thread B removes an element, execution crashes. This is non thread-safe because the client will have to synchronize on the intrinsic lock of X (synchronize(X)
) to have exclusive access. However, if the iterator returns a copy of the collection, the collection becomes thread-safe.
2例如,如果线程 A 迭代集合 X,而线程 B 删除一个元素,则执行会崩溃。这是非线程安全的,因为客户端必须同步 X ( synchronize(X)
)的内在锁才能进行独占访问。但是,如果迭代器返回集合的副本,则集合变为线程安全的。
3Immutable shared state, or mutable non shared objects are always thread-safe.
3不可变共享状态或可变非共享对象始终是线程安全的。
回答by Holly
Generally, @synchronized
guarantees thread safety, but only when used correctly. It is also safe to acquire the lock recursively, albeit with limitations I detail in my answer here.
通常,@synchronized
保证线程安全,但只有在正确使用的情况下。以递归方式获取锁也是安全的,尽管我在此处的回答中详细说明了一些限制。
There are several common ways to use @synchronized
wrong. These are the most common:
有几种常见的用法@synchronized
错误。这些是最常见的:
Using @synchronized
to ensure atomic object creation.
使用@synchronized
保证原子对象的创建。
- (NSObject *)foo {
@synchronized(_foo) {
if (!_foo) {
_foo = [[NSObject alloc] init];
}
return _foo;
}
}
Because _foo
will be nil when the lock is first acquired, no locking will occur and multiple threads can potentially create their own _foo
before the first completes.
因为_foo
在第一次获取锁时将为 nil,所以不会发生锁定,多个线程可能会_foo
在第一个完成之前创建自己的。
Using @synchronized
to lock on a new object each time.
使用@synchronized
每次一个新对象锁定。
- (void)foo {
@synchronized([[NSObject alloc] init]) {
[self bar];
}
}
I've seen this code quite a bit, as well as the C# equivalent lock(new object()) {..}
. Since it attempts to lock on a new object each time, it will always be allowed into the critical section of code. This is not some kind of code magic. It does absolutely nothing to ensure thread safety.
我已经看到了很多这段代码,以及 C# 等效的lock(new object()) {..}
. 由于它每次都尝试锁定一个新对象,因此它总是被允许进入代码的临界区。这不是某种代码魔法。它完全没有确保线程安全。
Lastly, locking on self
.
最后,锁定self
.
- (void)foo {
@synchronized(self) {
[self bar];
}
}
While not by itself a problem, if your code uses any external code or is itself a library, it can be an issue. While internally the object is known as self
, it externally has a variable name. If the external code calls @synchronized(_yourObject) {...}
and you call @synchronized(self) {...}
, you may find yourself in deadlock. It is best to create an internal object to lock upon that is not exposed outside of your object. Adding _lockObject = [[NSObject alloc] init];
inside your init function is cheap, easy, and safe.
虽然本身不是问题,但如果您的代码使用任何外部代码或本身就是一个库,则可能是一个问题。虽然对象在内部被称为self
,但它在外部有一个变量名。如果外部代码调用@synchronized(_yourObject) {...}
而您调用@synchronized(self) {...}
,您可能会发现自己陷入僵局。最好创建一个内部对象来锁定,它不会暴露在您的对象之外。_lockObject = [[NSObject alloc] init];
在 init 函数中添加是便宜、简单且安全的。
EDIT:
编辑:
I still get asked questions about this post, so here is an example of why it is a bad idea to use @synchronized(self)
in practice.
我仍然会被问到关于这篇文章的问题,所以这里有一个例子说明为什么@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);
}
It should be obvious to see why this happens. Locking on foo
and lock
are called in different orders on the foreground VS background threads. It's easy to say that this is bad practice, but if Foo
is a library, the user is unlikely to know that the code contains a lock.
很明显看到为什么会发生这种情况。在前台 VS 后台线程上以不同的顺序锁定foo
和lock
调用。很容易说这是不好的做法,但如果Foo
是库,用户不太可能知道代码包含锁。
回答by progrmr
@synchronized alone doesn't make code thread safe but it is one of the tools used in writing thread safe code.
@synchronized 单独不会使代码线程安全,但它是用于编写线程安全代码的工具之一。
With multi-threaded programs, it's often the case of a complex structure that you want to be maintained in a consistent state and you want only one thread to have access at a time. The common pattern is to use a mutex to protect a critical section of code where the structure is accessed and/or modified.
对于多线程程序,通常情况下是复杂的结构,您希望保持一致的状态,并且一次只希望一个线程可以访问。常见的模式是使用互斥锁来保护访问和/或修改结构的关键代码部分。
回答by progrmr
@synchronized
is thread safe
mechanism. Piece of code written inside this function becomes the part of critical section
, to which only one thread can execute at a time.
@synchronized
是thread safe
机制。写在这个函数中的一段代码成为 的一部分critical section
,一次只能执行一个线程。
@synchronize
applies the lock implicitly whereas NSLock
applies it explicitly.
@synchronize
隐式应用锁,而NSLock
显式应用它。
It only assures the thread safety, not guarantees that.What I mean is you hire an expert driver for you car, still it doesn't guarantees car wont meet an accident. However probability remains the slightest.
它只保证线程安全,不保证。我的意思是你为你的车雇了一个专业的司机,但它仍然不能保证汽车不会发生事故。然而概率仍然是最小的。
It's companion in GCD
(grand central dispatch) is dispatch_once
. dispatch_once does the same work as to @synchronized
.
它的同伴GCD
(大中央调度)是dispatch_once
。dispatch_once 与 to 做同样的工作@synchronized
。
回答by Parag Bafna
The @synchronized
directive is a convenient way to create mutex locks on the fly in Objective-C code.
该@synchronized
指令是一种在 Objective-C 代码中动态创建互斥锁的便捷方式。
side-effects of mutex locks:
互斥锁的副作用:
- deadlocks
- starvation
- 僵局
- 饥饿
Thread safety will depend on usage of @synchronized
block.
线程安全将取决于@synchronized
块的使用。