iOS關(guān)于線程加鎖方案

從前的鎖也好看
鑰匙精美有樣子
你鎖了 人家就懂了

一、存在的問題

1、實(shí)例

@interface TSDController ()

@property(nonatomic, assign) int totalMoney;

@end

@implementation TSDController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.totalMoney = 200;
    [self earnAndCostMoney];
    // Do any additional setup after loading the view.
}
- (void)earnAndCostMoney {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
     dispatch_async(queue, ^{
         for (int i = 0; i < 5; i++) {
             [self earnMoney];
         }
     });
     
     dispatch_async(queue, ^{
         for (int i = 0; i < 5; i++) {
             [self costMoney];
         }
     });
}
- (void)earnMoney {
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"掙40元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
}
- (void)costMoney {
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
}

打印結(jié)果(最后余額應(yīng)該是300的,出問題的結(jié)果不一定,此次是320):

花20元,余額180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
掙40元,余額220元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余額200元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
花20元,余額180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
掙40元,余額240元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
掙40元,余額260元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余額220元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
掙40元,余額300元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余額280元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
掙40元,余額320元---<NSThread: 0x60000005e680>{number = 6, name = (null)}

2、圖解

1、存在問題的結(jié)果.png

2、我們希望的結(jié)果.png

由上圖以及代碼可知,在多個(gè)線程同時(shí)對(duì)同一個(gè)對(duì)象做處理的時(shí)候,有可能出現(xiàn),多個(gè)任務(wù)在同一時(shí)間,沒有先后順序的對(duì)同一個(gè)對(duì)象進(jìn)行讀或?qū)懙牟僮?,這樣的話,就會(huì)出現(xiàn)上面的問題,而解決這一問題的辦法就是線程同步方案(線程鎖)

二、線程同步方案(線程鎖)

1、OSSPinLock: 自旋鎖, 性能很高,但是不推薦使用.

導(dǎo)入頭文件:#import <libkern/OSAtomic.h>
創(chuàng)建鎖:
@property (assign, nonatomic) OSSpinLock lock;
// 初始化鎖
self.lock = OS_SPINLOCK_INIT;
加鎖:OSSpinLockLock(&_lock);
添加關(guān)鍵代碼<><><>
解鎖:OSSPinLockUnLock(&_lock)
注意: lock 要做成 全局變量,要使用同一鎖才可以。如果有兩個(gè)方法:判斷兩個(gè)方法 是否能同時(shí)執(zhí)行(讀寫操作),如果需要這兩個(gè)方法不能同時(shí)執(zhí)行(讀寫操作) 則需要共用一把鎖。
原理:第二條線程 會(huì)等待 (此時(shí)會(huì)存在線程等待)第一條線程解鎖,才會(huì)繼續(xù)執(zhí)行。
p.s.OSSpinLock 的線程等待會(huì)處于忙等狀態(tài) (while(1);)會(huì)一直占有CPU 的資源 并沒有睡覺 休息。 目前此 鎖已經(jīng)不安全了 會(huì)出現(xiàn)問題:線程優(yōu)先級(jí)反轉(zhuǎn)問題。ios10 以后使用會(huì)警告?。

- (void)earnMoney {
    OSSpinLockLock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"掙40元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    OSSpinLockUnlock(&_lock);
}
- (void)costMoney {
    OSSpinLockLock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    OSSpinLockUnlock(&_lock);
}

打印結(jié)果:

掙40元,余額240元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
掙40元,余額280元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
掙40元,余額320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
掙40元,余額360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
掙40元,余額400元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余額380元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余額360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余額340元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余額320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余額300元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}

2. os_unfair_lock 現(xiàn)在它代替 OSSpinLock iOS 10 以后系統(tǒng)使用

導(dǎo)入頭文件:import<os/lcok.h>
注意:os_unfair_lock 是一個(gè)C語言的結(jié)構(gòu)體,如果要使用屬性調(diào)用需要使用assign修飾,從iOS10開始支持,用于取代不安全的OSSpinLock
// Low-level lock的特點(diǎn)等不到鎖就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
使用方法:初始化、加鎖、解鎖
寫法

- (void)earnMoney {
    os_unfair_lock_lock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"掙40元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    os_unfair_lock_unlock(&_lock);
}
- (void)costMoney {
    os_unfair_lock_lock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    os_unfair_lock_unlock(&_lock);
}

打印log和OSSPinLock一樣,注意:1、如果忘記解鎖,os_unfair_lock的線程會(huì)一直處于等待,出現(xiàn)線程死鎖現(xiàn)象。2、如果iOS 10 以下版本使用,會(huì)出現(xiàn)閃退現(xiàn)象

3、pthread_mutex :多平臺(tái)通用,跨平臺(tái):互斥鎖。等待鎖的線程或處于休眠狀態(tài)。區(qū)別于 自旋鎖(OSSpinLock),自旋鎖不會(huì)使線程休眠.

使用方式:導(dǎo)入頭文件:#import<pthread.h>
靜態(tài)初始化:pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER
但是不能直接 賦值給類屬性,因?yàn)?PTHREAD_MUTEX_INITIALIZER 是一個(gè)結(jié)構(gòu)體。
當(dāng)設(shè)置type為 PTHREAD_MUTEX_NORMAL為互斥鎖,當(dāng)設(shè)置為PTHREAD_MUTEX_RECURSIVE時(shí),為遞歸鎖
@property (assign, nonatomic) pthread_mutex_t mutex;
動(dòng)態(tài)初始化方式:

互斥鎖

       // 初始化屬性
       pthread_mutexattr_t attr;
       pthread_mutexattr_init(&attr);
       pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // normal
       // 初始化鎖
       pthread_mutex_init(&_mutex, &attr);
       // 銷毀屬性
       pthread_mutexattr_destroy(&attr);

注意: 這里的屬性設(shè)置pthread_mutex_init(&ticketMutex, NULL); 可以傳NULL,默認(rèn)就等同于:PTHREAD_MUTEX_DEFAULT

- (void)earnMoney {
    pthread_mutex_lock(&_mutex);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"掙40元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    pthread_mutex_unlock(&_mutex);
}
- (void)costMoney {
    pthread_mutex_lock(&_mutex);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    pthread_mutex_unlock(&_mutex);
}

最后注意把mutex 銷毀掉

- (void)dealloc
{
    pthread_mutex_destroy(& _mutex);
}

遞歸鎖

pthread_mutex的一種,只需要改變 mutex的屬性即可: 修改為:PTHREAD_MUTEX_RECURSIVE
注意

1>如果遞歸調(diào)用的方法里有用到鎖,那么就必須用遞歸鎖,否則會(huì)出現(xiàn)死鎖
2>同一個(gè)線程可以對(duì)遞歸鎖進(jìn)行重復(fù)上鎖

比如:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化屬性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // recursive
    // 初始化鎖
    pthread_mutex_init(&_mutex, &attr);
    // 銷毀屬性
    pthread_mutexattr_destroy(&attr);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive];
    });
}

- (void)recursive {
    // 將_mutex鎖上
    pthread_mutex_lock(&_mutex);
    static int count = 0;
    count++;
    if (count <= 5) {
        NSLog(@"%s---%@", __func__, [NSThread currentThread]);
        // 遞歸
        [self recursive];
    }
    
    // 將_mutex打開
    pthread_mutex_unlock(&_mutex);
}

// 打印
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}

pthread_cond(條件鎖)

  • 使用
@interface ViewController ()
@property (nonatomic, assign) pthread_cond_t cond;
@property (nonatomic, strong) NSMutableArray *mArray;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化鎖
    pthread_mutex_init(&_mutex, NULL);
    // 初始化條件
    pthread_cond_init(&_cond, NULL);
    [self condition];
}

- (void)condition {
    self.mArray = [NSMutableArray new];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_async(queue, ^{
        sleep(1.0);
        [self add];
    });
    dispatch_async(queue, ^{
        [self remove];
    });
}
- (void)add {
    // 將_mutex鎖上
    pthread_mutex_lock(&_mutex);
    [self.mArray addObject:@"1"];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 發(fā)送信號(hào)
    pthread_cond_signal(&_cond);
    // 將_mutex打開
    pthread_mutex_unlock(&_mutex);
}

- (void)remove {
    // 將_mutex鎖上
    pthread_mutex_lock(&_mutex);
    if (self.mArray.count == 0) {
        // 等待信號(hào)
        pthread_cond_wait(&_cond, &_mutex);
    }
    [self.mArray removeLastObject];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 將_mutex打開
    pthread_mutex_unlock(&_mutex);
}
- (void)dealloc {
    // 銷毀鎖
    pthread_mutex_destroy(&_mutex);
    // 銷毀條件
    pthread_cond_destroy(&_cond);
}

// 打印
-[ViewController add]---<NSThread: 0x6000005e3980>{number = 5, name = (null)}
-[ViewController remove]---<NSThread: 0x6000005944c0>{number = 7, name = (null)}

以上如果不加條件信號(hào)pthread_cond_signal和等待信號(hào)的話pthread_cond_wait,那么打印輸出的控制臺(tái)log的次序會(huì)正好相反

  • 說明

1>某個(gè)線程中的操作在某種情況下需要依賴另外一個(gè)線程中的操作,那么就可以用條件來實(shí)現(xiàn)
2>pthread_cond_wait函數(shù)會(huì)讓線程進(jìn)入休眠狀態(tài),并且將鎖打開
3>當(dāng)收到信號(hào)時(shí),pthread_cond_wait函數(shù)會(huì)喚醒線程,并且將鎖再次鎖上

4、NSLock

它是對(duì)pthread_mutex(normal)的封裝(互斥鎖)
使用:

@interface ViewController ()
@property (nonatomic, strong) NSLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化鎖
    self.lock = [NSLock new];
    [self earnAndCostMoney];
}

- (void) earnMoney {
    // 將lock鎖上
    [self.lock lock];
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"掙40元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    // 將lock打開
    [self.lock unlock];
}

- (void) costMoney {
    // 將lock鎖上
    [self.lock lock];
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余額%d元---%@", self.totalMoney, [NSThread currentThread]);
    // 將lock打開
    [self.lock unlock];
}

5、NSRecursiveLock

它是對(duì)pthread_mutex(recursive)的封裝
使用:

@interface ViewController ()
@property (nonatomic, strong) NSRecursiveLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化鎖
    self.lock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive];
    });
}

- (void)recursive {
    // 將lock鎖上
    [self.lock lock];
    
    static int count = 0;
    count++;
    if (count <= 5) {
        NSLog(@"%s---%@", __func__, [NSThread currentThread]);
        // 遞歸
        [self recursive];
    }
    
    // 將lock打開
    [self.lock unlock];
}

6、NSCondition

它是對(duì)pthread_mutexpthread_cond的封裝
使用:

@interface ViewController ()
@property (nonatomic, strong) NSCondition *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化鎖
    self.lock = [NSCondition new];
    [self condition];
}

- (void)add {
    // 將lock鎖上
    [self.lock lock];

    [self.mArray addObject:@"1"];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 發(fā)送信號(hào)
    [self.lock signal];

    // 將lock打開
    [self.lock unlock];
}

- (void)remove {
    // 將lock鎖上
    [self.lock lock];

    if (self.mArray.count == 0) {
        // 等待信號(hào)
        [self.lock wait];
    }
    [self.mArray removeLastObject];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 將lock打開
    [self.lock unlock];
}

7、NSConditionLock

它是對(duì)NSCondition的封裝,可以利用條件值來控制線程的執(zhí)行順序

@interface ViewController ()
@property (nonatomic, strong) NSConditionLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 條件值初始化為1
    self.lock = [[NSConditionLock alloc] initWithCondition:1];
    [self condition];
}

- (void)condition {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        [self one];
    });

    dispatch_async(queue, ^{
        [self two];
    });
    
    dispatch_async(queue, ^{
        [self three];
    });
}

- (void)one {
    // 條件值為3往下執(zhí)行,否則等待
    [self.lock lockWhenCondition:3];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 不設(shè)置條件值
    [self.lock unlock];
    
    NSLog(@"當(dāng)前條件值---%zd", self.lock.condition);
}

- (void)two {
    // 條件值為2往下執(zhí)行,否則等待
    [self.lock lockWhenCondition:2];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 條件值設(shè)置為3
    [self.lock unlockWithCondition:3];
    
    NSLog(@"當(dāng)前條件值---%zd", self.lock.condition);
}

- (void)three {
    // 條件值為1往下執(zhí)行,否則等待
    [self.lock lockWhenCondition:1];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 條件值設(shè)置為2
    [self.lock unlockWithCondition:2];
    
    NSLog(@"當(dāng)前條件值---%zd", self.lock.condition);
}

// 打印
-[ViewController three]---<NSThread: 0x600002ae7400>{number = 4, name = (null)}
當(dāng)前條件值---2
-[ViewController two]---<NSThread: 0x600002ae2680>{number = 6, name = (null)}
當(dāng)前條件值---3
-[ViewController one]---<NSThread: 0x600002adfa80>{number = 7, name = (null)}
當(dāng)前條件值---3

參考鏈接:iOS多線程:十種線程鎖, iOS多線程及線程同步方案(線程鎖)總結(jié)

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

相關(guān)閱讀更多精彩內(nèi)容

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