iOS-多線程(線程同步和線程安全)

線程同步和線程安全

線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解爲線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,於是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。

非線程安全(例子)

總共有100張火車票,有兩個售賣火車票的窗口,一個是北京火車票售賣窗口,另一個是上海火車票售賣窗口。兩個窗口同時售賣火車票,賣完爲止。

NSThread
/**
 * 初始化火車票數量、賣票窗口(非線程安全)、並開始賣票
 */
- (void)initTicketSaling{
    self.ticketSurplusCount = 100;
    self.ticketSaleWindowBJ = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.ticketSaleWindowBJ.name = @"北京火車票售票窗口";
    
    self.ticketSaleWindowSH = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.ticketSaleWindowSH.name = @"上海火車票售票窗口";

    [self.ticketSaleWindowBJ start];
    [self.ticketSaleWindowSH start];
}
/**
 * 售賣火車票(非線程安全)
 */
- (void)saleTicket{
    while (1) {
        if (self.ticketSurplusCount > 0){
            self.ticketSurplusCount --;
            NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            NSLog(@"所有火車票均已售完");
            break;
        }
    }
}

通過觀察打印結果,我們發現在不考慮線程安全的情況下,得到票數是錯亂的,並且部分票出現了重複。

GCD
/**
 * 初始化火車票數量、賣票窗口(非線程安全)、並開始賣票
 */
- (void)initTicketSaling{
    self.ticketSurplusCount = 100;
    //北京火車票售賣窗口
    dispatch_queue_t queueBJ = dispatch_queue_create("com.jun.bj", DISPATCH_QUEUE_SERIAL);
    //上海火車票售賣窗口
    dispatch_queue_t queueSH = dispatch_queue_create("com.jun.sh", DISPATCH_QUEUE_SERIAL);
    __weak typeof(self) weakSelf = self;
    dispatch_async(queueBJ, ^{
        [weakSelf saleTicket];
    });
    dispatch_async(queueSH, ^{
        [weakSelf saleTicket];
    });
}
/**
 * 售賣火車票(非線程安全)
 */
- (void)saleTicket{
    while(1){
        if(self.ticketSurplusCount > 0){
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            NSLog(@"所有火車票均已售完");
            break;
        }
    }
}

通過觀察打印結果,我們發現在不考慮線程安全的情況下,得到票數是錯亂的,並且部分票出現了重複。

線程安全

線程安全解決方案:可以給線程加鎖,在一個線程執行該操作的時候,不允許其他線程進行操作。iOS 實現線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/get等等各種方式。
以下代碼採用@synchronized方式。

NSThread
/**
 * 初始化火車票數量、賣票窗口(線程安全)、並開始賣票
 */
- (void)initTicketSaling{
    self.ticketSurplusCount = 100;
    self.ticketSaleWindowBJ = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.ticketSaleWindowBJ.name = @"北京火車票售票窗口";
    self.ticketSaleWindowSH = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.ticketSaleWindowSH.name = @"上海火車票售票窗口";
    [self.ticketSaleWindowBJ start];
    [self.ticketSaleWindowSH start];
}
/**
 * 售賣火車票(線程安全)
 */
- (void)saleTicket{
    while (1) {
        // 互斥鎖
        @synchronized (self) {
            if(self.ticketSurplusCount > 0){
                self.ticketSurplusCount --;
                NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
                [NSThread sleepForTimeInterval:0.2];
            }else{
                NSLog(@"所有火車票均已售完");
                break;
            }
        }
    }
}
GCD

以下代碼採用dispatch_semaphore方式。

/**
 * 線程安全:使用 semaphore 加鎖
 * 初始化火車票數量、賣票窗口(線程安全)、並開始賣票
 */
- (void)initTicketSaling{
    self.semaphoreLock = dispatch_semaphore_create(1);
    self.ticketSurplusCount = 100;
    //北京火車票售賣窗口
    dispatch_queue_t queueBJ = dispatch_queue_create("com.jun.bj", DISPATCH_QUEUE_SERIAL);
    //上海火車票售賣窗口
    dispatch_queue_t queueSH = dispatch_queue_create("com.jun.sh", DISPATCH_QUEUE_SERIAL);
    __weak typeof(self) weakSelf = self;
    dispatch_async(queueBJ, ^{
        [weakSelf saleTicket];
    });
    dispatch_async(queueSH, ^{
        [weakSelf saleTicket];
    });
}
/**
 * 售賣火車票(線程安全)
 */
- (void)saleTicket{
    while (1) {
        dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
        if(self.ticketSurplusCount > 0){
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            NSLog(@"所有火車票均已售完");
            dispatch_semaphore_signal(self.semaphoreLock);
            break;
        }
        dispatch_semaphore_signal(self.semaphoreLock);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章