iOS 鎖 部分一

主要講解OSSpinLock/os_unfair_lock/pthread_mutex_t鎖的基本用法

常見鎖的分類:

  • 自旋鎖OSSpinLock
  • 互斥鎖os_unfair_lock
  • 互斥/遞歸/條件鎖pthread_mutex_t
  • 互斥鎖NSLock
  • 遞歸鎖NSRecursiveLock
  • 條件鎖NSCondition
  • 條件鎖NSConditionLock
  • 遞歸鎖 @synchronized
  • 信號量semaphore
  • 讀寫鎖pthread_rwlock_t
  • 異步柵欄dispatch_barrier_async

iOS 鎖 部分一
iOS 鎖 部分二
iOS 鎖 部分三
iOS 鎖 部分四


1. 自旋鎖 OSSpinLock

特點:

  1. 不是一個安全的鎖, 等待鎖的線程會處于忙等(busy-wait)狀態(tài), 一直占用著CPU資源; (類似一個while(1)循環(huán)一樣,不停的查詢鎖的狀態(tài), 注意區(qū)分Runloop的機制, 同樣是阻塞, 但是Runloop是類似休眠的阻塞, 不會耗費CPU資源, 自旋鎖的這種忙等機制使它相比其他鎖效率更高, 因為沒有喚醒-休眠這些類似操作, 從而能更快去處理事情);

注意OSSpinLock自旋鎖iOS10之后已經(jīng)廢棄使用, 它可能會導(dǎo)致優(yōu)先級反轉(zhuǎn); 例如A/B兩個線程, A的優(yōu)先級大于B的, 我們的本意是A的任務(wù)優(yōu)先執(zhí)行; 但是使用OSSpinLock后, 如果是B先獲得了優(yōu)先訪問了共享資源獲得了鎖并加鎖, 而A線程再去訪問共享資源時鎖就會處于忙等狀態(tài), 由于優(yōu)先級高則它會一直占用CPU資源不會讓出時間片;這樣B一直不能獲得CPU資源去執(zhí)行任務(wù), 導(dǎo)致無法完成;

  1. 使用時需要導(dǎo)入頭文件#import <libkern/OSAtomic.h>
  2. 使用時可能會用到的方法:
    3.1 OS_SPINLOCK_INIT初始化自旋鎖;
    3.2 OSSpinLockLock()加鎖;
    3.3 OSSpinLockUnlock()解鎖;
    3.4 OSSpinLockTry()嘗試加鎖, 返回一個BOOL值;
  3. 示例代碼如下
#import "ViewController1.h"
#import <libkern/OSAtomic.h>
@interface ViewController1 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) OSSpinLock lock;
@end

@implementation ViewController1
- (void)viewDidLoad {
    [super viewDidLoad];
    self.money = 100;
    self.lock = OS_SPINLOCK_INIT;
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    //加鎖
    OSSpinLockLock(&_lock);
    [super saveMoney];
    //解鎖
    OSSpinLockUnlock(&_lock);
}
- (void)drawMoney {
    //加鎖
    OSSpinLockLock(&_lock);
    [super drawMoney ]; 
    //解鎖
    OSSpinLockUnlock(&_lock);
}
@end

2. 互斥鎖 os_unfair_lock

特點

  1. 設(shè)計宗旨在于替換OSSpinLock, 從 iOS10之后開始支持; 跟OSSpinLock不同, 等待os_unfari_lock的線程會處于休眠狀態(tài)(類似Runloop那這樣), 不是忙等;
  2. 使用時需要引入頭文件#import <os/lock.h>
  3. 使用時可能會用到的方法
    3.1 OS_UNFAIR_LOCK_INIT初始化鎖;
    3.2 os_unfair_lock_lock()加鎖;
    3.3 os_unfair_lock_unlock()解鎖;
    3.4 os_unfair_lock_trylock()嘗試加鎖, 返回一個BOOL值;
  4. 測試代碼
#import "ViewController2.h"
#import <os/lock.h>
@interface ViewController2 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) os_unfair_lock  lock;
@end
@implementation ViewController2
- (void)viewDidLoad {
    [super viewDidLoad];
    self.money = 100;
    self.lock = OS_UNFAIR_LOCK_INIT;
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    os_unfair_lock_lock(&_lock);
    [super saveMoney];
    os_unfair_lock_unlock(&_lock);
}
- (void)drawMoney {
    os_unfair_lock_lock(&_lock);
    [super drawMoney ];
    os_unfair_lock_unlock(&_lock);
}
@end

3. 互斥/遞歸/條件鎖 pthread_mutex_t

特點

  1. 跨平臺使用的API, 等待鎖的線程會處于休眠狀態(tài);
    當(dāng)使用遞歸鎖時: 允許同一個線程重復(fù)進行加鎖(另一個線程訪問時就會等待), 這樣可以保證多線程時訪問共用資源的安全性;
  2. 使用時需要引入頭文件#import <pthread.h>
  3. 使用時可能會用到的方法
    3.1 鎖的類型有
    ///普通互斥鎖
  #define PTHREAD_MUTEX_NORMAL      0
   ///錯誤檢查鎖(不清楚什么用)
  #define PTHREAD_MUTEX_ERRORCHECK  1
   ///遞歸鎖
  #define PTHREAD_MUTEX_RECURSIVE       2
  ///等同PTHREAD_MUTEX_NORMAL
  #define PTHREAD_MUTEX_DEFAULT     PTHREAD_MUTEX_NORMAL

3.2 鎖的初始化方法:

   ///初始化屬性
   pthread_mutexattr_t att ;
   pthread_mutexattr_init(&att);
   ///設(shè)置鎖的屬性
   pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
   ///初始化鎖
   pthread_mutex_init(&_lock, &att);

3.2 pthread_mutex_lock()加鎖
3.3 pthread_mutex_unlock()解鎖
3.4 pthread_mutexattr_destroy()銷毀屬性;
3.5 pthread_mutex_destroy()銷毀鎖;
3.6 pthread_cond_wait()等待條件(進入休眠的同時放開 mutex 鎖, 被喚醒的同時再次對 mutex 加鎖)
3.7 pthread_cond_signal()發(fā)送信號激活等待該條件的線程;
3.8 pthread_cond_broadcast()發(fā)送廣播信號激活等待該條件的所有線程;

  1. 關(guān)于互斥鎖遞歸鎖的測試代碼:
#import "ViewController3.h"
 #import <pthread.h>
@interface ViewController3 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) pthread_mutex_t  lock;
@property (nonatomic, assign) pthread_mutex_t  recursiveLock;
@end
@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];    
/******************************互斥鎖驗證******************************/
    self.money = 100;
        ///初始化屬性
    pthread_mutexattr_t att ;
    pthread_mutexattr_init(&att);
    ///設(shè)置鎖的屬性
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    ///初始化鎖
    pthread_mutex_init(&_lock, &att);
    ///銷毀屬性
     pthread_mutexattr_destroy(&att);
    [self hanleMoney];
/******************************遞歸鎖驗證******************************/
    ///初始化屬性
    pthread_mutexattr_t recursiveAtt ;
    pthread_mutexattr_init(&recursiveAtt);
    ///設(shè)置鎖的屬性
    pthread_mutexattr_settype(&recursiveAtt, PTHREAD_MUTEX_RECURSIVE);
    ///初始化鎖
    pthread_mutex_init(&_recursiveLock, &recursiveAtt);
    ///銷毀屬性
    pthread_mutexattr_destroy(&recursiveAtt);
    [self recuresiveAction];
}
- (void)hanleMoney {
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    pthread_mutex_lock(&_lock);
    [super saveMoney];
    pthread_mutex_unlock(&_lock);
}
- (void)drawMoney {
    pthread_mutex_lock(&_lock);
    [super drawMoney ];
    pthread_mutex_unlock(&_lock);
}
/*************************************遞歸鎖********************************************/
- (void)recuresiveAction {
    static int count = 10;
    pthread_mutex_lock(&_recursiveLock);
    NSLog(@"count: %d", count);
    if (count > 0) {
        count --;
        [self recuresiveAction];
    }
    pthread_mutex_unlock(&_recursiveLock);
}
/*************************************遞歸鎖********************************************/
- (void)dealloc {
    pthread_mutex_destroy(&_lock);
    pthread_mutex_destroy(&_recursiveLock);
}
@end
  1. 關(guān)于條件鎖的驗證代碼
    首先設(shè)定以下使用場景, 兩條線程 AB, A線程中執(zhí)行刪除數(shù)組元素, B線程中執(zhí)行添加數(shù)組元素;由于不知道哪個線程會先執(zhí)行, 所以需要加鎖實現(xiàn), 只有在添加后才能執(zhí)行刪除操作;為互斥鎖添加條件可以實現(xiàn);
    通過此方法可以實現(xiàn)線程依賴;
#import "ViewController4.h"
#import <pthread.h>

@interface ViewController4 ()
@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;
@end
@implementation ViewController4
- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     首先設(shè)定以下使用場景, 兩條線程 A和B, A線程中執(zhí)行刪除數(shù)組元素, B 線程中執(zhí)行添加數(shù)組元素;
     由于不知道哪個線程會先執(zhí)行, 所以需要加鎖實現(xiàn), 只有在添加后才能執(zhí)行刪除操作;為互斥鎖添加條件可以實現(xiàn);
     */
    ///初始化鎖
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&_lock, &att);
    pthread_mutexattr_destroy(&att);
    ///初始化條件
    pthread_cond_init(&_condition, NULL);
    NSThread *deThread = [[NSThread alloc] initWithTarget:self selector:@selector(deleteObj) object:nil];
    [deThread start];
    ///sleep一秒. 確保刪除元素的線程先獲得鎖;
    sleep(1);
    NSThread *adThread = [[NSThread alloc] initWithTarget:self selector:@selector(addObj) object:nil];
    [adThread start];
    
}
- (void)deleteObj {
    pthread_mutex_lock(&_lock);
    NSLog(@"delete begin");
    ///添加判斷, 如果沒有數(shù)據(jù)則添加條件
    if (self.dataArr.count < 1) {
        /**
         添加條件, 如果數(shù)組為空, 則添加等待線程休眠, 將鎖讓出;  接受到信號時會再次加鎖, 然后繼續(xù)向下執(zhí)行;
         */
        pthread_cond_wait(&_condition, &_lock);
    }
    
    [self.dataArr removeLastObject];
    NSLog(@"數(shù)組執(zhí)行刪除元素操作");
    pthread_mutex_unlock(&_lock);
}
- (void)addObj {
    pthread_mutex_lock(&_lock);
    NSLog(@"add begin");
    [self.dataArr addObject:@"HTI"];
    ///發(fā)送新號, 說明已經(jīng)添加元素了;
    pthread_cond_signal(&_condition);
    NSLog(@"數(shù)組執(zhí)行添加元素操作");
    pthread_mutex_unlock(&_lock);
}
- (void)dealloc {
    pthread_mutex_destroy(&_lock);
    pthread_cond_destroy(&_condition);
}
@end

補充. 為什么會出現(xiàn)死鎖?

當(dāng)兩個線程中其中一個線程獲得鎖并加鎖后, 如果使用完沒有解鎖, 則另一個線程就會一直處于等待解鎖狀態(tài), 就會形成死鎖;


文中測試代碼
參考文章
不再安全的OSSpinLock
objc4源碼下載地址
libplatform源碼下載地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容