iOS —— 多線程應用,ios多線程應用
一、共用資源
共用資源 : 就是記憶體中的一塊資源同時被多個進程所訪問,而每個進程可能會對該資源的資料進行修改
問題 : 如果 線程A 訪問了某塊資源 C,並且修改了其中的資料,此時 線程B 也訪問了 資源C,並且也對 C 中的資料進行了修改;那麼等到 線程A 和 線程B 執行結束後,此時,資源C 中的資料就並不是最初的設定了
此時,如果使用者想擷取 被 線程A 修改的 資源C 的資料,但是 資源C 的資料也被 線程B 修改了,所以獲得的是錯誤的資料;如果使用者想擷取 被 線程B 修改的 資源C 的資料,但是 資源C 的資料也被 線程A 修改了,所以獲得的是錯誤的資料
下面看代碼,以出售火車票為例
建立火車票資料模型
1 // LHTicketModal.h 2 #import <Foundation/Foundation.h> 3 4 @interface LHTicketModal : NSObject 5 6 @property (nonatomic) NSUInteger ticketCount; // 剩餘票數 7 @property (nonatomic) NSUInteger ticketSaleCount; // 賣出數量 8 9 @end10 11 // LHTicketModal.m12 #import "LHTicketModal.h"13 14 static const NSUInteger kTicketTotalCount = 50;15 16 @implementation LHTicketModal17 18 - (instancetype)init {19 20 self = [super init];21 22 if (self) {23 24 // 初始化剩餘票數應該為總數25 _ticketCount = kTicketTotalCount;26 27 // 初始化賣出票數應該為 028 _ticketSaleCount = 0;29 30 }31 32 return self;33 34 }35 36 @end
UIViewController 代碼
1 // LHSharedViewController.h 2 #import "LHSharedViewController.h" 3 #import "LHTicketModal.h" 4 5 @interface LHSharedViewController () 6 7 // 車票模型 8 @property (nonatomic, strong) LHTicketModal * ticket; 9 10 // 線程對象11 @property (nonatomic, strong) NSThread * threadBJ;12 @property (nonatomic, strong) NSThread * threadSH;13 14 @end15 16 // LHSharedViewController.m17 @implementation LHSharedViewController18 19 - (void)viewDidLoad {20 21 [super viewDidLoad];22 23 // 1. 初始化 票數模型對象24 _ticket = [[LHTicketModal alloc] init];25 26 // 2. 初始化 線程對象 並設定線程名27 _threadBJ = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:@"北京賣出"];28 29 _threadBJ.name = @"北京";30 31 _threadSH = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets:) object:@"上海賣出"];32 33 _threadSH.name = @"上海";34 35 }36 37 // 線程執行的方法38 - (void)sellTickets:(NSString *)str {39 40 while (YES) {41 42 // 判斷剩餘量是否大於 0,大於 0 才可以賣出43 if (_ticket.ticketCount > 0) {44 45 // 暫停一段時間46 [NSThread sleepForTimeInterval:0.2];47 48 // 賣出一張票,剩餘票數減 1,賣出票數加 149 _ticket.ticketCount--;50 51 _ticket.ticketSaleCount++;52 53 // 擷取當前進程54 NSThread * currentThread = [NSThread currentThread];55 56 NSLog(@"%@ 賣了一張票,還剩 %lu 張票", currentThread.name, _ticket.ticketCount);57 58 }59 60 }61 62 }63 64 // 按鈕的動作方法65 - (IBAction)startSaleBtnClick:(id)sender {66 67 // 開啟線程68 [_threadBJ start];69 70 [_threadSH start];71 72 73 }
點擊按鈕後,線程開始運行,結果
看一看出,共用資源同時被多個線程訪問並修改,導致使用者取得了錯誤的資料
那麼解決這種情況的辦法就是 加鎖
加鎖是指對一段代碼進行加鎖,加鎖之後,若一個線程已經在對共用資源的資料修改,此時就不會再有其他線程來訪問該資源進行修改,直至當前線程已經結束修改資源的代碼時,其他進程才可以對其進行修改
可以把共用資源看做一個房間,線程看做人;當一個人進入房間之後就會鎖門(加鎖),對房間進行各種布置,此時其他人是進不來的,因為沒有鑰匙;直至當前的人出房間,其他的人才可以進房間進行布置
加鎖的方式有多種,這裡介紹兩種
方式一 : 使用 @synchronized (加鎖對象) {} 關鍵字
只需修改上述代碼的 sellTickets 方法,其餘不變,這裡將其方法名改為 sellTickets_v2
1 - (void)sellTickets_v2:(NSString *)str { 2 3 while (YES) { 4 5 // 對當前對象加鎖 6 @synchronized (self) { 7 8 // 判斷剩餘量是否大於 0,大於 0 才可以賣出 9 if (_ticket.ticketCount > 0) {10 11 // 暫停一段時間12 [NSThread sleepForTimeInterval:0.2];13 14 // 賣出一張票,剩餘票數減 1,賣出票數加 115 _ticket.ticketCount--;16 17 _ticket.ticketSaleCount++;18 19 // 擷取當前進程20 NSThread * currentThread = [NSThread currentThread];21 22 NSLog(@"%@ 賣了一張票,還剩 %lu 張票", currentThread.name, _ticket.ticketCount);23 24 }25 26 }27 28 }29 30 }
再次運行程式,點擊按鈕。結果
方式二 : 使用 NSCondition 類
在 類擴充中聲明並在 viewDidLoad 方法中初始化
1 - (void)sellTickets_v3:(NSString *)str { 2 3 while (YES) { 4 5 // 使用 NSCondition 加鎖 6 [_ticketCondition lock]; 7 8 // 判斷剩餘量是否大於 0,大於 0 才可以賣出 9 if (_ticket.ticketCount > 0) {10 11 // 暫停一段時間12 [NSThread sleepForTimeInterval:0.2];13 14 // 賣出一張票,剩餘票數減 1,賣出票數加 115 _ticket.ticketCount--;16 17 _ticket.ticketSaleCount++;18 19 // 擷取當前進程20 NSThread * currentThread = [NSThread currentThread];21 22 NSLog(@"%@ 賣了一張票,還剩 %lu 張票", currentThread.name, _ticket.ticketCount);23 24 }25 26 // 使用 NSCondition 解鎖27 [_ticketCondition unlock];28 29 }30 31 }
運行結果和上述一樣
使用 NSCondition 時,將加鎖的代碼放在 loac 和 unlock 中間
總結 :
1. 互斥鎖的優缺點
優點 : 有效防止因多線程搶奪資源造成的資料安全問題
缺點 : 需要消耗大量 CPU 資源
2. 互斥鎖使用的前提
多個線程搶奪同一資源
線程同步,互斥鎖就是使用了線程同步