簡體   English   中英

Objective-C 中的原子屬性與線程安全

[英]Atomic properties vs thread-safe in Objective-C

在我讀過的大多數討論中,它表明使屬性原子化並不能保證它是線程安全的,它只是保證返回的值不會因為一個對象寫入它而另一個對象寫入而成為垃圾試圖同時閱讀它。

我知道這不是線程安全的,因為第三個對象可能正在寫入它,雖然訪問它的對象不會收回垃圾,但由於多個對象同時寫入它,它並不完全確定它將返回哪個值時間,它可能會得到它們的任何值。

因此,當我們說它不會返回垃圾時,垃圾是否意味着如果一個對象是非原子的並且一個對象試圖訪問它而另一個正在寫入它,它可能會在寫入過程中返回結果,並且只得到寫入所帶來變化的部分、不完整的版本? 這就是“垃圾”在這個意義上的含義嗎,以及哪些原子特性有助於防止?

Objective C 中的atomic屬性保證您永遠不會看到部分寫入。 @property具有atomic屬性時,不可能只寫入部分值。 二傳手是這樣的:

- (void)setProp:(NSString *)newValue {
    [_prop lock];
    _prop = newValue;
    [_prop unlock];
}

因此,如果兩個線程要同時寫入值@"test"@"otherTest" ,那么在任何給定時間屬性只能是屬性的初始值或@"test"@"otherTest" nonatomic更快,但該值是垃圾值,並且沒有@"test" / @"otherTest" (thx @Gavin) 或任何其他垃圾值的部分字符串。

但是atomic只是使用簡單的線程安全的。 這不是保證。 Appledoc說:

考慮一個XYZPerson對象,其中一個人的名字和姓氏都使用來自一個線程的原子訪問器進行更改。 如果另一個線程同時訪問這兩個名稱,原子 getter 方法將返回完整的字符串(不會崩潰),但不能保證這些值將是相對於彼此的正確名稱。 如果在更改之前訪問了名字,但在更改之后訪問了姓氏,那么您最終會得到一對不一致、不匹配的名稱。

我從來沒有遇到過使用原子的問題。 我以這種方式設計了代碼,原子屬性沒有問題。

回答您的第三段; 基本上是的。 線程正在寫入原子序數時無法讀取原子序數。

例如,如果一個線程已寫入原子四字節數的前兩個字節,並且在另一個線程上請求讀取該數字,則該讀取必須等到所有四個字節都已寫入。

相反,如果一個線程寫入了一個非原子四字節數的前兩個字節,並且此時在另一個線程上請求讀取該數,它將讀取前兩個新數據字節,但將獲得舊數據來自其他兩個字節中的先前寫入操作。

羅伯特哈維的回答是正確的,但考慮到人們經常錯過的一個子案例。 考慮這段代碼:

#import <Foundation/Foundation.h>

@interface Test : NSObject
@property (readwrite, strong) NSMutableArray *atomicArray;
@property (nonatomic, readwrite, strong) NSMutableArray *nonatomicArray;
@end

@implementation Test
@end

int main() {
    @autoreleasepool {
        Test *t = [[Test alloc] init];

        NSMutableArray *a = [[NSMutableArray alloc] init];
        [t setAtomicArray:a];
        [a release];

        NSMutableArray *one = [t atomicArray];
        [t setAtomicArray: nil];
        [one addObject:@"Test"];

        a = [[NSMutableArray alloc] init];
        [t setNonatomicArray:a];
        [a release];

        NSMutableArray *two = [t nonatomicArray];
        [t setNonatomicArray:nil];
        [two addObject:@"Test"];
    }
}

除了阻止您讀取部分寫入的值外,原子屬性還阻止您獲取您無法控制其生命周期的對象(它們通過保留然后自動釋放對象來做到這一點)。 這在像我鏈接的示例這樣的單線程代碼中很重要,但在多線程代碼中更重要,因為另一個線程可能導致對象從您的下方釋放出來。

在並發編程中:

原子意味着如果在某個線程(線程#1)和其他線程(線程#2)中訪問屬性值以進行寫入操作,則嘗試訪問原子值以進行讀取或寫入操作,然后其他線程(線程#2)等待直到線程#1 完成任務。 換句話說,原子以先到先得的方式同步屬性的訪問。

非原子意味着如果在某個線程(線程#1)和其他線程(線程#2)中訪問屬性值以進行寫入操作,則嘗試訪問非原子值以進行讀取或寫入操作,然后其他線程(線程#2)獲取值立即獲得舊值

明確實施

@property(原子,保留)NSNumber *count

會是這樣的

- (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, 保留) NSNumber *count

像這樣

- (NSNumber *)count {
    return _count;
}

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

非原子屬性不是線程安全的,並且會直接返回它們的屬性。 這將比原子屬性更快,但如果不采取預防措施顯然會帶來一些風險。

這些非原子屬性的 setter 和 getter

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM