[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Locks and multi-threaded accessors
--Apple-Mail-13-647801211
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
> Here are my proposed thread safe versions:
>
> - (NSString *)title
> {
> return [[_myTitle retain] autorelease];
> }
(I already had replied to Erik on a related question, but for the
record...)
You have to protect _myTitle with the same lock used in the setter.
But the lock can and should as tight as possible, as Matt points out.
Below is a write-up which discusses this.
Ali
Thread safety tends to be tricky. One thing to remember when making
getters and setters thread-safe is that it is possible for retain,
release, or copy methods to do a lot of stuff, if overridden by the
classes. For instance, release might end up calling dealloc, and who
knows what that does. So it's best to try to avoid calling out too much
while locked. It's also interesting to think about the implications of
reentrancy; what happens if the set method is called while within the
set method?
Here are possible thread-safe getter/setter implementations for simple
instance variables:
- (NSString *)title {
id val;
lock;
val = [instanceVar retain];
unlock;
return [val autorelease];
}
- (void)setTitle:(NSString *)newVal {
id val = [newVal copy]; // or retain, depending on usage
id old;
lock;
old = instanceVar;
instanceVar = val;
unlock;
[old release];
}
Note that the set method here does not do an equality-check. If this
is desired (which is a good idea if the method is often called with the
same value as before, and the cost of copy is higher than the cost of
the locks), the following setter can be used instead. It simply checks
for equality while locked, before blindly doing a copy. But, if all you
are doing is a retain, the equality check is not worth it:
- (void)setTitle:(NSString *)newVal {
id val, old;
lock;
if (newVal == instanceVar) {
unlock;
return;
}
unlock;
val = [newVal copy];
lock;
old = instanceVar;
instanceVar = val;
unlock;
[old release];
}
The only callout above while locked is in the getter, and that is a
retain call, which is hopefully almost never a problem (release and
copy are more likely to do more work). In the very unlikely event that
a callback to these methods is possible from that retain callout, a
recursive lock might be a better choice than a regular lock.
Note that the above technique applies to immutable object instance
variables. If a class hands out mutable variables which it then can
mutate, then we're looking at additional issues: For instance, while
the caller is enumerating the subviews list it received from a view, if
the view mutates the list by adding or removing a new subview, that's a
problem. There are several possible solutions:
1. Assure that all clients who access and cause changes to that data
structure do it with an external lock. Basically this puts the burden
on the caller, which is not unreasonable in many cases. In the above
example, the caller would be responsible for using a lock or other
means of synchronization in accessing and changing the subviews list.
2. Do not hand out the mutable instance variable, but instead return a
copy by doing copy/autorelease in the getter. As a general solution,
especially when used with potentially large collections, this will
probably get expensive. Might work for easy-to-copy mutable objects.
3. Hand out the mutable instance variable, but lazily make a copy when
a mutation is needed. This can be accomplished either by building this
copy-on-write behavior into the instance variable (a copy-on-write
NSMutableArray, for instance), or by actually remembering a
per-instance-variable flag in the object which indicates that the
mutable instance variable was handed out and should be copied before
being modified.
--Apple-Mail-13-647801211
Content-Transfer-Encoding: 7bit
Content-Type: text/enriched;
charset=US-ASCII
<excerpt>Here are my proposed thread safe versions:
- (NSString *)title
{
return [[_myTitle retain] autorelease];
}
</excerpt>
(I already had replied to Erik on a related question, but for the
record...)
You have to protect _myTitle with the same lock used in the setter.
But the lock can and should as tight as possible, as Matt points out.
Below is a write-up which discusses this.
Ali
Thread safety tends to be tricky. One thing to remember when making
getters and setters thread-safe is that it is possible for retain,
release, or copy methods to do a lot of stuff, if overridden by the
classes. For instance, release might end up calling dealloc, and who
knows what that does. So it's best to try to avoid calling out too
much while locked. It's also interesting to think about the
implications of reentrancy; what happens if the set method is called
while within the set method?
Here are possible thread-safe getter/setter implementations for simple
instance variables:
<fixed> - (NSString *)title {
id val;
lock;
val = [instanceVar retain];
unlock;
return [val autorelease];
}
- (void)setTitle:(NSString *)newVal {
id val = [newVal copy]; // or retain, depending on usage
id old;
lock;
old = instanceVar;
instanceVar = val;
unlock;
[old release];
}
</fixed>
Note that the set method here does not do an equality-check. If this
is desired (which is a good idea if the method is often called with
the same value as before, and the cost of copy is higher than the cost
of the locks), the following setter can be used instead. It simply
checks for equality while locked, before blindly doing a copy. But, if
all you are doing is a retain, the equality check is not worth it:
<fixed> - (void)setTitle:(NSString *)newVal {
id val, old;
lock;
if (newVal == instanceVar) {
unlock;
return;
}
unlock;
val = [newVal copy];
lock;
old = instanceVar;
instanceVar = val;
unlock;
[old release];
}
</fixed>
The only callout above while locked is in the getter, and that is a
retain call, which is hopefully almost never a problem (release and
copy are more likely to do more work). In the very unlikely event that
a callback to these methods is possible from that retain callout, a
recursive lock might be a better choice than a regular lock.
Note that the above technique applies to immutable object instance
variables. If a class hands out mutable variables which it then can
mutate, then we're looking at additional issues: For instance, while
the caller is enumerating the subviews list it received from a view,
if the view mutates the list by adding or removing a new subview,
that's a problem. There are several possible solutions:
1. Assure that all clients who access and cause changes to that data
structure do it with an external lock. Basically this puts the burden
on the caller, which is not unreasonable in many cases. In the above
example, the caller would be responsible for using a lock or other
means of synchronization in accessing and changing the subviews list.
2. Do not hand out the mutable instance variable, but instead return a
copy by doing copy/autorelease in the getter. As a general solution,
especially when used with potentially large collections, this will
probably get expensive. Might work for easy-to-copy mutable objects.
3. Hand out the mutable instance variable, but lazily make a copy when
a mutation is needed. This can be accomplished either by building this
copy-on-write behavior into the instance variable (a copy-on-write
NSMutableArray, for instance), or by actually remembering a
per-instance-variable flag in the object which indicates that the
mutable instance variable was handed out and should be copied before
being modified.
--Apple-Mail-13-647801211--