关于ios:Objective-C中的原子属性与线程安全性

Atomic properties vs thread-safe in Objective-C

在我读过的大多数讨论中,它表明使属性原子化并不能保证它是线程安全的,它只是保证返回的值不会因为一个对象向其写入而被垃圾化,而另一个对象试图同时读取它。

我知道这不是线程安全的,因为第三个对象可能正在写它,虽然访问它的对象不会返回垃圾,但不完全确定当多个对象同时在写它时它将返回哪个值,它可能会得到它们的任何值。

所以当我们说它不会返回垃圾时,垃圾是否意味着,如果一个对象是非原子的,而另一个对象试图访问它,而另一个对象正在写入它,那么它可能会在写入过程中得到返回的结果,并且只得到由写入引起的部分、不完整的更改版本?从这个意义上来说,这就是"垃圾"的含义吗?原子属性有助于防止什么?


目标C中的atomic属性保证永远不会看到部分写入。当@property具有atomic属性时,不可能只部分写入该值。设置器是这样的:

1
2
3
4
5
- (void)setProp:(NSString *)newValue {
    [_prop lock];
    _prop = newValue;
    [_prop unlock];
}

因此,如果两个线程希望同时写入值@"test"和@"othertest",则在任何给定时间,属性只能是属性的初始值或@"test"或@"othertest"。nonatomic更快,但该值是一个垃圾值,没有@"test"/@"othertest"(thx@gavin)或任何其他垃圾值的部分字符串。

但是atomic只有简单的使用才是线程安全的。这不是车库。AppleDoc说:

Consider an XYZPerson object in which both a person’s first and last
names are changed using atomic accessors from one thread. If another
thread accesses both names at the same time, the atomic getter methods
will return complete strings (without crashing), but there’s no
guarantee that those values will be the right names relative to each
other. If the first name is accessed before the change, but the last
name is accessed after the change, you’ll end up with an inconsistent,
mismatched pair of names.

我在使用原子弹时从来没有遇到过问题。我就是这样设计代码的,原子属性没有问题。


回答你的第三段;基本上是的。当线程正在写入原子数时,无法读取原子数。

例如,如果一个线程已写入原子四字节数的前两个字节,并且在另一个线程上请求读取该数字,则该读取必须等待所有四个字节都写入。

相反,如果一个线程写入了一个非原子四字节数的前两个字节,并且此时在另一个线程上请求读取该数字,则它将读取前两个新的数据字节,但将从另两个字节的前一个写入操作中获取旧数据。


罗伯特·哈维的回答是正确的,但有一种情况是人们经常会怀念的。请考虑以下代码:http://pastebin.com/s7xyjm6g

除了阻止您读取部分写入的值之外,原子属性还阻止您获取不控制其生存期的对象(它们通过保留对象然后自动释放对象来完成此操作)。这在单线程代码(如我链接的示例)中很重要,但在多线程代码中更重要,在多线程代码中,另一个线程可能导致对象从您下面释放。


显式实现

@property (atomic, retain) NSNumber *count

就这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSNumber *)count {
    NSNumber *count;
    @synchronized(self) {
        count = [_count retain]; // +1
    }
    return [count autorelease]; // delayed -1
}

- (void)setCount:(NSNumber *)count {
    id oldValue;
    @synchronized(self) {
        oldValue = _count;
        _count = [count retain];
    }
    [oldValue release];
}

原子属性是属性的默认行为。原子属性在获取或设置值时添加线程安全级别。也就是说,无论其他线程在做什么,属性的getter和setter都将始终完全完成。与非原子等价物相比,这些属性的访问速度要慢一些。

明确地说,我们将

@property (nonatomic, retain) NSNumber *count

这样地

1
2
3
4
5
6
7
8
9
10
11
- (NSNumber *)count {
    return _count;
}

- (void)setCount:(NSNumber *)count {
    if (count != _count) {
        id oldValue = _count;
        _count = [count retain];
        [_oldValue release];
    }
}

非原子属性不是线程安全的,将直接返回其属性。这将比原子性质更快,但如果不采取预防措施,显然会带来一些风险。

这些非原子属性的setter和getter


在并行编程中:

原子意味着,如果某个线程(线程1)和其他线程(线程2)中的写入操作访问的属性值试图访问读操作或写操作的原子值,则其他线程(线程2)将等待直到线程1完成其任务。换句话说,原子同步属性的访问是以先到先服务为基础的。

非原子性意味着,如果在某个线程(线程1)和其他线程(线程2)中为写入操作而访问的属性值试图为读取或写入操作访问非原子值,则其他线程(线程2)立即获取值并获取旧值。