線程同步和線程安全
線程安全:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程同步:可理解爲線程 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);
}
}