多執行緒六——加鎖方案一 : OSSpinLock
阿新 • • 發佈:2018-12-20
一、iOS 中的執行緒同步方案 -> 加鎖
- OSSpinLock:自旋鎖
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
二、OSSpinLock
- 含義
- OSSpinLock叫做”自旋鎖”,等待鎖的執行緒會處於忙等(busy-wait)狀態,一直佔用著CPU資源
- 目前已經不再安全,可能會出現優先順序反轉問題
- 如果等待鎖的執行緒優先順序較高,它會一直佔用著CPU資源,優先順序低的執行緒就無法釋放鎖
- 需要匯入標頭檔案#import <libkern/OSAtomic.h>
- 主要程式碼
三、 示例程式: 賣票
- 希望關鍵程式加鎖
沒加鎖程式碼:
/// 賣1張票 - (void)saleTicket { int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"還剩 %d 張票 -%@", oldTicketsCount, [NSThread currentThread]); } /// 賣票演示 - (void)ticketTest { self.ticketsCount = 15; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0 ; i < 3; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0 ; i < 3; i++) { [self saleTicket]; } }); dispatch_async(queue, ^{ for (int i = 0 ; i < 3; i++) { [self saleTicket]; } }); }
加鎖程式碼: 加鎖: 別的執行緒無法再進行訪問 解鎖: 用完後,解除鎖定,讓其他執行緒可以訪問。 如果使用須匯入 #import <libkern/OSAtomic.h>
第一次加鎖:在 saleTicket 方法中 ,一開始就加鎖
/// 賣1張票 - (void)saleTicket { // 加鎖 - 初始化 - iOS10 已經棄用 OSSpinLock look = OS_SPINLOCK_INIT; // 加鎖, 因為要傳的是指標,所以傳入地址 OSSpinLockLock(&look); // 原來程式碼 int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@"還剩 %d 張票 -%@", oldTicketsCount, [NSThread currentThread]); // 解鎖 OSSpinLockUnlock(&look); }
執行結果:
- 可以看到,這樣的加鎖 ,跟沒有加一樣,票數還是錯誤的。
- 這是因為
OSSpinLock look = OS_SPINLOCK_INIT;
是區域性變數,每次進入到saleTicket
這個方法的時候,都會重新建立。 - 修改為:
@property(nonatomic,assign) OSSpinLock look;
- (void)viewDidLoad {
[super viewDidLoad];
// 加鎖 - 初始化 - iOS10 已經棄用
_look = OS_SPINLOCK_INIT;
[self ticketTest];
}
/// 賣1張票
- (void)saleTicket {
// 加鎖, 因為要傳的是指標,所以傳入地址
OSSpinLockLock(&_look);
// 原來程式碼
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"還剩 %d 張票 -%@", oldTicketsCount, [NSThread currentThread]);
// 解鎖
OSSpinLockUnlock(&_look);
}
列印結果:
- 列印結果可以看到:剩餘票數正常
四、示例程式:存錢取錢 *沒有加鎖的 寫法
- (void)viewDidLoad {
[super viewDidLoad];
// 加鎖 - 初始化 - iOS10 已經棄用
_look = OS_SPINLOCK_INIT;
[self moneyTest];
}
/// 存錢,取錢演示
- (void)moneyTest {
self.money = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self drawMoney];
}
});
}
/// 取錢
- (void)drawMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取 20 , 還剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
}
/// 存錢
- (void)saveMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存 50 , 還剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
}
執行結果:沒有對的
- 應該如何加鎖?
- 是 存錢一把鎖,取錢一把鎖
- 還是 他們兩個共用一把鎖
思考: * 存錢取錢 是否能同時執行? * 不能 * 這兩個操作在同一時間段只能執行一個 。 * 所以可以使用一把鎖 * 如果 鎖不一樣,意味著 同一時間段可以兩個一起執行。 * 只有大家共用一把鎖,才能保證同一時間段只能執行一個操作。
加鎖之後的 程式碼
- (void)viewDidLoad {
[super viewDidLoad];
// 加鎖 - 初始化 - iOS10 已經棄用
_look = OS_SPINLOCK_INIT;
[self moneyTest];
}
/// 存錢,取錢演示
- (void)moneyTest {
self.money = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 5; i++) {
[self drawMoney];
}
});
}
/// 取錢
- (void)drawMoney {
OSSpinLockLock(&_look);
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取 20 , 還剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
// 解鎖
OSSpinLockUnlock(&_look);
}
/// 存錢
- (void)saveMoney {
OSSpinLockLock(&_look);
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存 50 , 還剩 %d 元 - %@",oldMoney, [NSThread currentThread]);
// 解鎖
OSSpinLockUnlock(&_look);
}
五、存錢取錢 和 賣票
如果存錢取錢 和 賣票 使用同一把鎖,會出現什麼事情?
- 意味著,同一時間段,只能執行一個操作,要不存錢,要不取錢,要不 賣票
- 但沒有必要,效率會降低
- 因為 存錢取錢 和 賣票 不是一個事情,訪問的變數不一樣。賣票訪問的是 ticketsCount 變數。 存錢取錢 訪問的是 money 變數。兩個互不相干。
- 只有當多個執行緒訪問同一個變數(資源)時,才需要使用同一把鎖。