atomic的實(shí)現(xiàn)機(jī)制

atomic作用:多線程下將屬性設(shè)置為atomic可以保證讀取數(shù)據(jù)的一致性。因?yàn)樗麑⒈WC數(shù)據(jù)只能被一個線程占用,也就是說一個線程對屬性進(jìn)行寫操作時,會使用自旋鎖鎖住該屬性。不允許其他的線程對其進(jìn)行讀取操作了。
但是它有一個很大的缺點(diǎn):因?yàn)樗褂米孕i鎖住該屬性,因此它會消耗更多的資源,性能會很低。要比nonatomic慢20倍。

內(nèi)部實(shí)現(xiàn):property 的 atomic 是采用 spinlock_t (自旋鎖)實(shí)現(xiàn)的。
// getter方法

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) 
{
    // ...
    if (!atomic) return *slot;

    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    // ...
}

//setter方法

// setter
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // ...
    if (!atomic) 
{
        oldValue = *slot;
        *slot = newValue;
    } 
else 
{
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
    // ...
}

小結(jié)

簡而言之,atomic的作用只是給getter和setter加了個鎖,atomic只能保證代碼進(jìn)入getter或者setter函數(shù)內(nèi)部時是安全的,一旦出了getter和setter,多線程安全只能靠程序員自己保障了。所以atomic屬性和使用property的多線程安全并沒什么直接的聯(lián)系。另外,atomic由于加鎖也會帶來一些性能損耗,所以我們在編寫iOS代碼的時候,一般聲明property為nonatomic,在需要做多線程安全的場景,自己去額外加鎖做同步。

指針Property指向的內(nèi)存區(qū)域

property可以分為值類型和引用類型。值類型就是一些基本的數(shù)據(jù)類型。而引用類型就是指的各種對象。聲明為指針,指向這個屬性所在的內(nèi)存區(qū)域。
因此當(dāng)我們訪問一個屬性的時候,有可能訪問的是指針本身,還有可能訪問的是這個指針指向的內(nèi)存區(qū)域。
如:

self.userName = @"nick";

這里訪問的就是指針本身,對指針進(jìn)行賦值。

[self.userName rangeOfString:@"peak"];

是在訪問指針指向的字符串所在的內(nèi)存區(qū)域。
指針本身也是占用內(nèi)存的,是固定的8個字節(jié)。
而指針指向的內(nèi)存區(qū)域占用的內(nèi)存就無法固定了,有可能很大,也有可能很小。我們暫且定為n。

下面舉例說明一下:

@property (atomic) NSArray *array;

atomic修飾的實(shí)際上是這個指針,也就是占8個字節(jié)內(nèi)存的指針,因此就不可能隨意使用多線程來操作這塊內(nèi)存的。因?yàn)檫@塊內(nèi)存是原子性的。是線程安全的。
真正不安全的是指針指向的那塊內(nèi)存區(qū)域,他是非原子性的,當(dāng)多個線程去操作這塊內(nèi)存的時候,就會出現(xiàn)不安全的情況。
下面我們再看兩個例子:

@property (atomic, strong) NSString*                 stringA;

//thread A
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.stringA = @"a very long string";
    }
    else {
        self.stringA = @"string";
    }
    NSLog(@"Thread A: %@\n", self.stringA);
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.stringA.length >= 10) {
        NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
    }
    NSLog(@"Thread B: %@\n", self.stringA);
}

因?yàn)閍tomic修飾的是指針區(qū)域,而不是內(nèi)存區(qū)域,但是我們通過self.stringA.lengthsubstringWithRange訪問的是property的內(nèi)存區(qū)域,在前一刻讀length的時候self.stringA = @"a very long string";,下一刻取substring的時候線程A已經(jīng)將self.stringA = @"string";,立即出現(xiàn)out of bounds的Exception,crash。主要原因就是self.stringA.length >= 10NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];代碼之間內(nèi)存區(qū)域被其他線程修改了。

同樣的場景還存在對集合類操作的時候,比如:

@property (atomic, strong) NSArray*                 arr;

//thread A
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
    NSLog(@"Thread A: %@\n", self.arr);
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.arr.count >= 2) {
        NSString* str = [self.arr objectAtIndex:1];
    }
    NSLog(@"Thread B: %@\n", self.arr);
}

同理,即使我們在訪問objectAtIndex之前做了count的判斷,線程B依舊很容易crash,原因也是由于前后兩行代碼之間arr所指向的內(nèi)存區(qū)域被其他線程修改了。

所以真正要操心的是這一類內(nèi)存區(qū)域的訪問,即使用atomatic聲明屬性也沒用。

如何做到多線程安全?(加鎖)

為了避免這種crash可以通過一下代碼:

//thread A
[_lock lock];
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.stringA = @"a very long string";
    }
    else {
        self.stringA = @"string";
    }
    NSLog(@"Thread A: %@\n", self.stringA);
}
[_lock unlock];

//thread B
[_lock lock];
if (self.stringA.length >= 10) {
    NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
}
[_lock unlock];

//thread A
[_lock lock];
for (int i = 0; i < 100000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
    NSLog(@"Thread A: %@\n", self.arr);
}
[_lock unlock];
    
//thread B
[_lock lock];
if (self.arr.count >= 2) {
    NSString* str = [self.arr objectAtIndex:1];
}
[_lock unlock];

參考鏈接1
參考鏈接2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,674評論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,262評論 8 265
  • 最全的iOS面試題及答案 iOS面試小貼士 ———————————————回答好下面的足夠了-----------...
    大羅Rnthking閱讀 1,052評論 0 2
  • 一年多了吧,才把簡書又下載回來??醋约阂荒昵皩懙臇|西覺得又可笑又幼稚。現(xiàn)在這么看來,一年的成長還是看得見。 自己現(xiàn)...
    沒事兒寫寫字閱讀 261評論 0 0
  • 玩具店里生意火爆老板娘待客熱情一個個前來選購?fù)婢叩男∨笥押团闼麄兦皝淼母赣H被她哄得笑意掛在臉上興趣發(fā)自心海店里涌起...
    狀元大人閱讀 407評論 2 4

友情鏈接更多精彩內(nèi)容