多線程 (三)iOS中的鎖


鎖的類別:互斥鎖,遞歸鎖,條件鎖,自旋鎖等

鎖的實現方式:NSLock,NSRecursiveLock, NSConditionLock,@synchronized,GCD的信號量等

下面說一下常用的幾種鎖:

1.@synchronized:對象級別所,互斥鎖,性能較差不推薦使用

@synchronized(這里添加一個OC對象,一般使用self) {

        這里寫要加鎖的代碼

    }

  @synchronized使用注意點

  1.加鎖的代碼盡量少

  2.添加的OC對象必須在多個線程中都是同一對象,下面舉一個反例

- (void)viewDidLoad {
    [super viewDidLoad];
    //設置票的數量為5
    _tickets = 5;
    //線程一
    NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    threadOne.name = @"threadOne";
    //線程二
    NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    
    //開啟線程
    [threadOne start];
    [threadTwo start]; 
}
- (void)saleTickets
{
    NSObject *object = [[NSObject alloc] init];
    while (1)
    {
        @synchronized(object) {
            [NSThread sleepForTimeInterval:1];
            if (_tickets > 0)
            {
                _tickets--;
                NSLog(@"剩余票數= %ld",_tickets);
            }
            else
            {
                NSLog(@"票賣完了");
                break;
            }
        }
    }    
}

 結果賣票又出錯了,出現這個原因的問題是每個線程都會創建一個object對象,鎖后面加的object在不同線程中就不同了;

把@synchronized(object)改成 @synchronized(self)就能得到了正確結果

2.NSLock:互斥鎖,

@interface ViewController ()
{
    NSLock *mutexLock;
}

@property (assign, nonatomic)NSInteger tickets;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創建鎖
    mutexLock = [[NSLock alloc] init];
    
    //設置票的數量為5
    _tickets = 5;
    //線程一
    NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    threadOne.name = @"threadOne";
    //線程二
    NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    
    //開啟線程
    [threadOne start];
    [threadTwo start];  
}
- (void)saleTickets
{
    while (1)
    {
        [NSThread sleepForTimeInterval:1];
        //加鎖
        [mutexLock lock];
        if (_tickets > 0)
        {
            _tickets--;
            NSLog(@"剩余票數= %ld",_tickets);
        }
        else
        {
            NSLog(@"票賣完了");
            break;
        }
        //解鎖
        [mutexLock unlock];    
    }
}

 NSLock: 使用注意,不能多次調用 lock方法,會造成死鎖

 

3.NSRecursiveLock:遞歸鎖

@interface ViewController ()
{
    NSRecursiveLock *rsLock;
}

@property (assign, nonatomic)NSInteger tickets;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創建鎖遞歸鎖
    rsLock = [[NSRecursiveLock alloc] init];    
    //設置票的數量為5
    _tickets = 5;
    //線程一
    NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    threadOne.name = @"threadOne";
    //線程二
    NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    //開啟線程
    [threadOne start];
    [threadTwo start];    
}
- (void)saleTickets
{
    while (1)
    {
        [NSThread sleepForTimeInterval:1];
        //加鎖,遞歸鎖可以多次加鎖
        [rsLock lock];
        [rsLock lock];
        if (_tickets > 0)
        {
            _tickets--;
            NSLog(@"剩余票數= %ld",_tickets);
        }
        else
        {
            NSLog(@"票賣完了");
            break;
        }
        //解鎖,只有對應次數解鎖,其他線程才能訪問。
        [rsLock unlock];
        [rsLock unlock];   
    }    
}

 

4.NSConditionLock:條件鎖

 

NSConditionLock:條件鎖,一個線程獲得了鎖,其它線程等待。

 

 [xxxx lock]; 表示 xxx 期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內部的condition) 那它能執行此行以下代碼,如果已經有其他線程獲得鎖(可能是條件鎖,或者無條件鎖),則等待,直至其他線程解鎖

 

 [xxx lockWhenCondition:A條件]; 表示如果沒有其他線程獲得該鎖,但是該鎖內部的condition不等於A條件,它依然不能獲得鎖,仍然等待。如果內部的condition等於A條件,並且沒有其他線程獲得該鎖,則進入代碼區,同時設置它獲得該鎖,其他任何線程都將等待它代碼的完成,直至它解鎖。

 

 [xxx unlockWithCondition:A條件]; 表示釋放鎖,同時把內部的condition設置為A條件

 

@interface ViewController () {
   
    NSConditionLock *_cdtLock; //條件鎖

}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //創建條件鎖
    _cdtLock = [[NSConditionLock alloc] init];
    [NSThread detachNewThreadSelector:@selector(conditionLockAction1) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(conditionLockAction2) toTarget:self withObject:nil];
    
}

- (void)conditionLockAction1 {
    
    //阻塞線程2s
    [NSThread sleepForTimeInterval:2];
    
    for (NSInteger i = 0; i < 3; i++) {
       
        //加鎖
        [_cdtLock lock];
        
        NSLog(@"i = %li", i);
        
        //釋放鎖,並設置condition屬性的值為i
        [_cdtLock unlockWithCondition:i];
        
    }
}

- (void)conditionLockAction2 {
    
    //當標識為2時同步代碼段才能夠執行,如果標識為其它數字則當前線程被阻塞。
    [_cdtLock lockWhenCondition:2];
    
    NSLog(@"thread2");
    
    [_cdtLock unlock];
    
}

 打印結果:

如果我們把代碼中[_cdtLock lockWhenCondition:2]換成[_cdtLock lockWhenCondition:1]則會發現出現如下結果

和我們預想的在i = 1后面打印thread2不符合,這是因為conditionLockAction1中的代碼段也需要獲得鎖,同時在循環執行過后把condition置成了2,那么conditionLockAction2就再也沒機會加鎖了,所以不打印thread2。

我們可以靠下面的代碼驗證

@interface ViewController () {
   
    NSConditionLock *_cdtLock; //條件鎖

}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //創建條件鎖
    _cdtLock = [[NSConditionLock alloc] init];
    [NSThread detachNewThreadSelector:@selector(conditionLockAction1) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(conditionLockAction2) toTarget:self withObject:nil];
    
}

- (void)conditionLockAction1 {
    
    //阻塞線程2s
    [NSThread sleepForTimeInterval:2];
    
    for (NSInteger i = 0; i < 3; i++) {
       
        //加鎖
        [_cdtLock lock];
        
        NSLog(@"i = %li", i);
        
        //釋放鎖,並設置condition屬性的值為i
        [_cdtLock unlockWithCondition:i];
        //在i 為 1的時候阻塞線程1s
        if (i == 1)
        {
            [NSThread sleepForTimeInterval:1];
        }
        
    }
}

- (void)conditionLockAction2 {
    
    //當標識為2時同步代碼段才能夠執行,如果標識為其它數字則當前線程被阻塞。
    [_cdtLock lockWhenCondition:1];
    
    NSLog(@"thread2");
    
    [_cdtLock unlock];
    
}

 現在的結果就和我們預期的一樣了

 

5.NSCondition:可以理解為互斥鎖和條件鎖的結合

 用生產者消費者中的例子可以很好的理解NSCondition

1.生產者要取得鎖,然后去生產,生產后將生產的商品放入庫房,如果庫房滿了,則wait,就釋放鎖,直到其它線程喚醒它去生產,如果沒有滿,則生產商品后調用signal,可以喚醒在此condition上等待的線程。

 2.消費者要取得鎖,然后去消費,如果當前沒有商品,則wait,釋放鎖,直到有線程去喚醒它消費,如果有商品,則消費后會通知正在等待的生產者去生產商品。

 生產者和消費者的關鍵是:當庫房已滿時,生產者等待,不再繼續生產商品,當庫房已空時,消費者等待,不再繼續消費商品,走到庫房有商品時,會由生產者通知消費來消費。

- (void)conditionTest
{
    //創建數組存放商品
    products = [[NSMutableArray alloc] init];
    condition = [[NSCondition alloc] init];
    
    [NSThread detachNewThreadSelector:@selector(createProducter) toTarget:self withObject:nil];
    [NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:self withObject:nil];
}

- (void)createConsumenr
{
    while (1) {
        //模擬消費商品時間,讓它比生產慢一點
        [NSThread sleepForTimeInterval:arc4random()%10 * 0.1 + 1.5];
        [condition lock];
        while (products.count == 0) {
            NSLog(@"商品為0,等待生產");
            [condition wait];
        }
        [products removeLastObject];
        NSLog(@"消費了一個商品,商品數 = %ld",products.count);
        [condition signal];
        [condition unlock];
    }
    
}

- (void)createProducter
{
    while (1) {
        //模擬生產商品時間
        [NSThread sleepForTimeInterval:arc4random()%10 * 0.1 + 0.5];
        [condition lock];
        while (products.count == 5)
        {
            NSLog(@"商品滿了,等待消費");
            [condition wait];
        }
        [products addObject:[[NSObject alloc] init]];
        NSLog(@"生產了一個商品,商品數%ld",products.count);
        [condition signal];
        [condition unlock];
    }
    
}

 

了解死鎖

  概念:死鎖是指兩個或兩個以上的進程(線程)在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。

產生死鎖的4個必要條件

 

1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。

 

2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

 

3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

 

4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM