iOS多線程操作時一些要注意的安全問題

hqmxlyx 8年前發布 | 12K 次閱讀 多線程 iOS開發 移動開發

這次STMAssembleView https://github.com/ming1016/STMAssembleView 加入異步解析上線后發現一些線程安全方面的問題,現總結下。 先看看這段代碼

- (void)viewDidLoad {
    [super viewDidLoad];

    self.asStr = @"string is very first";

    [self performSelector:@selector(doSomething) withObject:nil afterDelay:0.5];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"before sleep %@",self.asStr);
        sleep(3);
        NSLog(@"after sleep %@",self.asStr);
    });
}

- (void)doSomething {
    self.asStr = @"string has changed";
}

執行結果

2016-08-31 20:58:43.927 HomePageTest[68018:535737] before sleep string is very first
2016-08-31 20:58:46.927 HomePageTest[68018:535737] after sleep string has changed

會發現在異步執行中如果asStr改變了,那么異步線程里的asStr也會改變這樣就沒法保證異步對資源獨占操作。

如果在異步block里創建一個str賦值如下代碼

- (void)viewDidLoad {
    [super viewDidLoad];

    self.asStr = @"string is very first";

    [self performSelector:@selector(doSomething) withObject:nil afterDelay:0.5];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSString *str = self.asStr;
        NSLog(@"before sleep %@",str);
        sleep(3);
        NSLog(@"after sleep %@",str);
    });
}

- (void)doSomething {
    self.asStr = @"string has changed";
}

執行結果

2016-08-31 20:59:50.094 HomePageTest[68075:537624] before sleep string is very first
2016-08-31 20:59:53.097 HomePageTest[68075:537624] after sleep string is very first

這樣新的string就不會受到外部改變的影響,但是如果在這個賦值時刻self.asStr已變成野指針那么后面的操作還是會出錯,雖然這樣情況不是那么容易出現。

如何防止這種情況呢,可以看看下面的代碼

- (void)viewDidLoad {
    [super viewDidLoad];

    self.asStr = @"string is very first";

    [self performSelector:@selector(doSomething) withObject:nil afterDelay:0.5];

    __weak __typeof(self.asStr) weakString = self.asStr;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        __strong __typeof(weakString) strongString = weakString;
        if(strongString) {
            NSLog(@"before sleep %@",strongString);
            sleep(3);
            NSLog(@"after sleep %@",strongString);
        }
    });
}

- (void)doSomething {
    self.asStr = @"string has changed";
}

執行結果

2016-08-31 21:00:24.457 HomePageTest[68131:538976] before sleep string is very first
2016-08-31 21:00:27.461 HomePageTest[68131:538976] after sleep string is very first

weakString會在self.asStr釋放時置為nil,如果不是nil時,能夠確保對象在block調用的完整周期里面被retain,如果被搶占對strongString的執行會繼續并且會產生一樣的值,如果strongString執行到時是nil,那么block不能正確執行前已經返回,這樣就不會出現先前那樣的問題。

還可以用鎖來保證多個線程對一份資源在操作時不會被更改

@interface HomeViewController () {
    pthread_mutex_t _mutex;
}
@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutex_init(&_mutex, NULL);
    self.asStr = @"string is very first";

    [self performSelector:@selector(doSomething) withObject:nil afterDelay:0.5];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        pthread_mutex_lock(&_mutex);
        self.asStr = @"string has changed in global queue";
        NSLog(@"before sleep %@",self.asStr);
        sleep(3);
        NSLog(@"after sleep %@",self.asStr);
        pthread_mutex_unlock(&_mutex);

    });

}

- (void)doSomething {
    pthread_mutex_lock(&_mutex);
    self.asStr = @"string has changed";
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
}

執行結果

2016-08-31 21:01:00.351 HomePageTest[68174:540038] before sleep string has changed in global queue
2016-08-31 21:01:03.354 HomePageTest[68174:540038] after sleep string has changed in global queue

在RAC中使用的是OSSpinLock來保證線程安全的,不過幾位蘋果工程師在swift-dev郵件列表中討論weak屬性的線程安全問題的郵件里爆出自旋鎖有bug,郵件地址: https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html 。大概就是不同優先級線程調度算法會有優先級反轉問題,比如低優先級獲鎖訪問資源,高優先級嘗試訪問時會等待,這時低優先級又沒法爭過高優先級導致任務無法完成lock釋放不了。也可以看看ReactiveCo社區的討論 https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2619

本來OSSpinLock是性能最高的鎖,但是由于如果不在同一個優先級線程進行鎖操作就不能保證安全,那么dispatch_semaphore和pthread_mutex這種僅次于自旋鎖的可以作為替代方案。我注意到非死book的KVOController在2016年5月17日時的一個Commit里將所有OSSpinLock替換成了pthread_mutex,可參看這個commit https://github.com/非死book/KVOController/commit/4f5c329b26f48b151eed82da085288763e2e1761 。pthread_mutex會在新系統中性能得到很大的提升,所以可以考慮這個方案。

 

來自:http://www.starming.com/index.php?v=index&view=102

 

 本文由用戶 hqmxlyx 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!