線程安全是怎么產(chǎn)生的
每個線程都擁有它自己的執(zhí)行堆棧酷勺,由內(nèi)核調(diào)度獨立的運行時間片。一個線程可 以和其他線程或其他進程通信扳躬,執(zhí)行 I/O 操作脆诉,甚至執(zhí)行任何你想要它完成的任務(wù)。 因為它們處于相同的進程空間贷币,所以一個獨立應(yīng)用程序里面的所有線程共享相同的虛 擬內(nèi)存空間击胜,并且具有和進程相同的訪問權(quán)限。線程編程的危害之一是在多個線程之間的資源爭奪役纹。如果多個線程在同一個時間 試圖使用或者修改同一個資源偶摔,就會出現(xiàn)問題。緩解該問題的方法之一是消除共享資源促脉。
同步工具
為了防止不同線程意外修改數(shù)據(jù)辰斋,你可以設(shè)計你的程序沒有同步問題,或你也可 以使用同步工具瘸味。盡管完全避免出現(xiàn)同步問題相對更好一點宫仗,但是幾乎總是無法實現(xiàn)。 以下個部分介紹了你可以使用的同步工具的基本類別旁仿。
1.原子操作(atomic
)
我們在聲明一個變量的時候一般會使用nonatomic
藕夫,這個就是非原子操作;原子操作是atomic
枯冈。
簡單的加減使用原子操作具有更高的性能優(yōu)勢汁胆。注意是加減,不是增刪K住嫩码!
也就是說僅僅對于getter
,setter
是線程安全的,兩個線程都去對變量賦值是安全的罪既。對于比如NSMutableArray
類型的增刪操作不是線程安全的
2.內(nèi)存屏障和 Volatile
變量
因為內(nèi)存屏障和 volatile
變量降低了編譯器可執(zhí)行的優(yōu)化铸题,因此你應(yīng)該謹慎使 用它們铡恕,只在有需要的地方時候,以確保正確性
3.鎖Lock
鎖是最常用的同步工具丢间。你可以是使用鎖來保護臨界區(qū)(critical section)探熔,這 些代碼段在同一個時間只能允許被一個線程訪問。比如烘挫,一個臨界區(qū)可能會操作一個 特定的數(shù)據(jù)結(jié)構(gòu)诀艰,或使用了每次只能一個客戶端訪問的資源
3.1 信號量 Dispatch Semaphore
我的上一篇文章中有講過一次性搞懂 GCD的所有用法
使用
dispatch_semaphore_signal
加1dispatch_semaphore_wait
減1,為0時等待的設(shè)置方式來達到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題饮六。
dispatch_semaphore_create(long value)
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema)
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout)
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執(zhí)行其后的語句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"線程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"線程1 發(fā)送信號");
NSLog(@"--------------------------------------------------------");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"線程2");
dispatch_semaphore_signal(signal);
NSLog(@"線程2 發(fā)送信號");
});
關(guān)于信號量其垄,我們可以用停車來比喻:
停車場剩余4個車位,那么即使同時來了四輛車也能停的下卤橄。如果此時來了五輛車绿满,那么就有一輛需要等待。
信號量的值(signal
)就相當于剩余車位的數(shù)目窟扑,dispatch_semaphore_wait
函數(shù)就相當于來了一輛車喇颁,dispatch_semaphore_signal
就相當于走了一輛車。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value)
)嚎货,調(diào)用一次dispatch_semaphore_signal
橘霎,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait
剩余車位就減少一個殖属;當剩余車位為 0 時茎毁,再來車(即調(diào)用dispatch_semaphore_wait
)就只能等待。有可能同時有幾輛車等待一個停車位忱辅。有些車主沒有耐心七蜘,給自己設(shè)定了一段等待時間,這段時間內(nèi)等不到停車位就走了墙懂,如果等到了就開進去停車橡卤。而有些車主就像把車停在這,所以就一直等下去损搬。
3.2 POSIX
互斥鎖
POSIX
互斥鎖在程序里面很容易使用碧库。
- 導入頭文件
#import <pthread.h>
- 聲明并
pthread_mutex_t mutex
, - 初始化
pthread_mutex_init(&mutex, NULL)
-
pthread_mutex_lock
和pthread_mutex_unlock
函數(shù), 進行加鎖解鎖 *pthread_mutex_destroy
釋放該鎖的數(shù)據(jù)結(jié)構(gòu)。
#import <pthread.h>
@interface MYPOSIXViewController ()
{
/** 聲明pthread_mutex_t的結(jié)構(gòu) */
pthread_mutex_t mutex;
}
@end
@implementation MYPOSIXViewController
- (void)dealloc{
/** 釋放該鎖的數(shù)據(jù)結(jié)構(gòu) */
pthread_mutex_destroy(&mutex);
}
- (void)viewDidLoad {
[super viewDidLoad];
/** 初始化 */
pthread_mutex_init(&mutex, NULL);
}
- (void)getIamgeName:(NSMutableArray *)imageNames{
NSString *imageName;
/** 加鎖 */
pthread_mutex_lock(&mutex);
if (imageNames.count>0) {
imageName = [imageNames firstObject];
[imageNames removeObjectAtIndex:0];
}
/** 解鎖 */
pthread_mutex_unlock(&mutex);
}
3.3 NSLock
互斥鎖
在 Cocoa 程序中 NSLock
中實現(xiàn)了一個簡單的互斥鎖巧勤。所有鎖(包括 NSLock
)的 接口實際上都是通過NSLocking
協(xié)議定義的嵌灰,它定義了 lock
和unlock
方法。你使用 這些方法來獲取和釋放該鎖颅悉。
除了標準的鎖行為沽瞭,NSLock
類還增加了tryLock
和 lockBeforeDate:
方法。方法 tryLock
試圖獲取一個鎖剩瓶,但是如果鎖不可用的時候驹溃,它不會阻塞線程城丧。相反,它只 是返回 NO
豌鹤。而 lockBeforeDate:
方法試圖獲取一個鎖亡哄,但是如果鎖沒有在規(guī)定的時間 內(nèi)被獲得,它會讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回 NO
)布疙。
NSLock
頭文件中只有如下方法蚊惯,成員變量
- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
3.4 NSCondition
條件鎖
NSCondition
同樣實現(xiàn)了NSLocking
協(xié)議,所以它和NSLock
一樣灵临,也有NSLocking
協(xié)議的lock
和unlock
方法截型,可以當做NSLock
來使用解決線程同步問題,用法完全一樣俱诸。同時菠劝,NSCondition
提供更高級的用法赊舶。wait
和signal
睁搭,和條件信號量類似。
@interface TestViewController ()
/*
創(chuàng)建一個數(shù)組盛放生產(chǎn)的數(shù)據(jù)笼平,創(chuàng)建一個線程鎖
*/
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *products;
@end
@implementation TestViewController
#pragma mark - event reponse
/*
拖拽一個點擊事件园骆,創(chuàng)建兩個線程
*/
- (IBAction)coditionTest:(id)sender {
NSLog(@"condiction 開始");
[NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(createProducter) toTarget:self withObject:nil];
}
#pragma mark - provate methods
- (void)createConsumenr{
[self.condition lock];
while(self.products.count == 0){
NSLog(@"等待產(chǎn)品");
[_condition wait];
}
[self.products removeObject:0];
NSLog(@"消費產(chǎn)品");
[_condition unlock];
}
- (void)createProducter{
[self.condition lock];
[self.products addObject:[[NSObject alloc] init]];
NSLog(@"生產(chǎn)了一個產(chǎn)品");
[_condition signal];
[_condition unlock];
}
#pragma mark - getters and setters
- (NSMutableArray *)products {
if(_products == nil){
_products = [[NSMutableArray alloc] initWithCapacity:0];
}
return _products;
}
- (NSCondition *)condition {
if(_condition == nil){
_condition = [[NSCondition alloc] init];
}
return _condition;
}
@end
3.5 NSRecursiveLock
遞歸鎖
遞歸鎖可以被同一線程多次請求,而不會引起死鎖寓调,這主要是用在循環(huán)或遞歸操作中锌唾。
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value){
[theLock lock];
if (value != 0){
--value;
MyRecursiveFunction(value);
}
MyRecursiveFunction(5);
3.6 @synchronized
寫法最簡單 @synchronized( xx )
, 也是性能消耗最高的鎖
- (void)getIamgeName:(int)index{
NSString *imageName;
@synchronized(self) {
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObject:imageName];
}
}
}
3.7 OSSpinLock
被公認為不安全,不做描述
線程安全總結(jié)
- 不可改變的對象一般是線程安全的。一旦你創(chuàng)建了它們夺英,你可以把這些對象在線程間安全的傳遞晌涕。另一方面,可變對象通常不是線程安全的痛悯。為了在多線程應(yīng)用 里面使用可變對象余黎,應(yīng)用必須適當?shù)耐健?/li>
- 許多對象在多線程里面不安全的使用被視為是”線程不安全的”。只要同一時間 只有一個線程载萌,那么許多這些對象可以被多個線程使用惧财。這種被稱為專門限制應(yīng) 用程序的主線程的對象通常被這樣調(diào)用。
- 應(yīng)用的主線程負責處理事件扭仁。盡管ApplicationKit在其他線程被包含在事件路 徑里面時還會繼續(xù)工作垮衷,但操作可能會被打亂順序。