1. 程式人生 > >IOS多執行緒使用GCD與訊號量實現生產者與消費者模式

IOS多執行緒使用GCD與訊號量實現生產者與消費者模式

一、原理的簡述  

在生產者消費者模式當中,首先需要分清在這個模式當中有哪些角色?

各角色分別擔任什麼職責與它們之間的關係如何?

角色之間是在保證資料的準確性的情況下如何通訊(同步資料)的?

假設現在有一個這樣的情形:

有兩個人共同訪問一個容量有限的倉庫,這2個人,其中一個是生產鞋子的,另一個是售賣鞋子,

他們共同使用一個倉庫。在使用這個倉庫之前,這2人之間需要建立一種規約,即:

1.生產鞋子的人首先需要向倉庫管理員申請鑰匙,在拿到鑰匙的時候需要判斷兩種情況

在滿倉的時候不能再讓生產鞋子的人繼續往這個倉庫中存放鞋子,因為這個倉庫的容量有限,

繼續放會出現爆倉,那麼這個時候生產鞋子的人應該怎麼做呢?很明顯,讓它把倉庫鑰匙

交出來,

然後出去等待。當倉庫出現有位置空的時候,才繼續存放鞋子。

2.賣鞋子的時候在使用這個倉庫之前也需要向倉庫管理員申請鑰匙,當倉庫有鞋子的時候,

把鞋子拿出來去賣,否則歸還鑰匙出倉等待。

3.這個倉庫只有一把鑰匙,即同時只能限制一個人進來。

二、將問題程式化:

.h檔案:

//
//  BaseRoom.h
//  MultiThread
//
//  Created by liuxiaobing on 2018/10/31.
//  Copyright © 2018 liuxiaobing. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN


/**
 倉庫
 */
@interface BaseRoom : NSObject


@property(strong,nonatomic) NSMutableArray* list;

@property(assign,nonatomic) BOOL bIsStop;

@property(strong,nonatomic) NSMutableArray* baseList;           //倉庫
@property(strong,nonatomic) dispatch_semaphore_t mutex;         //訪問倉庫(臨界區)的互斥訪問訊號量
@property(strong,nonatomic) dispatch_semaphore_t comsumer_sem;      //生產者-是否生產物件的標記  消費者是否消費倉庫物件的標記 使用信
@property(strong,nonatomic) dispatch_semaphore_t product_sem;      //生產者-是否生產物件的標記  消費者是否消費倉庫物件的標記 使用信
@property(nonatomic,assign) int count;

-(void) produce:(NSString*) e;

-(NSString*) comsumer;

@end

NS_ASSUME_NONNULL_END

.m檔案 :

//
//  BaseRoom.m
//  MultiThread
//
//  Created by liuxiaobing on 2018/10/31.
//  Copyright © 2018 liuxiaobing. All rights reserved.
//

#import "BaseRoom.h"

@implementation BaseRoom

- (instancetype)init
{
    self = [super init];
    if (self) {
        
        //新建一個倉庫,這裡暫不作容量設計
        self.baseList = [[NSMutableArray alloc] init];
        
        //初始化生產物件--消費者標記,初始為0表示什麼都沒有
        self.product_sem = dispatch_semaphore_create(10);
        
         self.comsumer_sem = dispatch_semaphore_create(0);
        
        //初始化臨界區互斥訪問訊號量,用訊號量實現互斥,特殊初始值為1.
        //控制同一時刻只有一個執行緒物件在訪問倉庫
        self.mutex = dispatch_semaphore_create(1);
        
    }
    return self;
}

-(void)produce:(NSString *)e{
    
    
     long baseCount = dispatch_semaphore_wait(self.mutex,  5 * NSEC_PER_SEC);        //先獲取訪問倉庫的訊號量
    if(baseCount != 0){
        NSLog(@"39----------倉庫有人正在使用,生產者處於等待");
    }
     long maxSpaceCount = dispatch_semaphore_wait(self.product_sem, 5 * NSEC_PER_SEC);  //再判斷 倉庫是否還有可放物品的空間
    
    if(maxSpaceCount != 0){
        NSLog(@"43----------倉庫10個空間已經使用完,生產者處於等待:倉庫容量:%lu",[self.baseList count]);
    }else{
        [self.baseList addObject:e];
        NSLog(@"40---------生產鞋子%@:w倉庫目前有:%lu",e,[self.baseList count]);
        dispatch_semaphore_signal(self.mutex);          //生產完了釋放臨界區的訪問鎖
        dispatch_semaphore_signal(self.comsumer_sem);    //將倉庫中的皮鞋數量加1
    }
    
    
}

-(NSString*) comsumer{
    NSString* e = nil;
    long baseCount = dispatch_semaphore_wait(self.mutex, 5 * NSEC_PER_SEC);        //先獲取訪問倉庫的訊號量
    if(baseCount != 0){
        NSLog(@"55----------倉庫有人正在使用,消費者處於等待");
    }
    long avableCount = dispatch_semaphore_wait(self.comsumer_sem, 5 * NSEC_PER_SEC);  //再判斷 倉庫是否還有可取,如果有物品,則取一個出來,否則t等待
    if(avableCount != 0){
        NSLog(@"59----------空倉,消費者處於等待");
    }else{
         e = [self.baseList objectAtIndex:[self.baseList count] -1];
        [self.baseList removeLastObject];
        NSLog(@"50---------賣鞋子:%@ 倉庫還有%lu:",e,[self.baseList count]);
        dispatch_semaphore_signal(self.mutex);          //生產完了釋放臨界區的訪問鎖
        dispatch_semaphore_signal(self.product_sem);    //將倉庫中的可放置的數量 +1
    }
    
    
    return e;
}


@end

測試頁面:

.h檔案

//
//  ProComsumerModel.h
//  MultiThread
//
//  Created by liuxiaobing on 2018/10/31.
//  Copyright © 2018 liuxiaobing. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "BaseRoom.h"

NS_ASSUME_NONNULL_BEGIN


/**
 生產者消費者模式
 */
@interface ProComsumerModel : UIViewController

@property(nonatomic,strong) BaseRoom* baseroom;

//生產者執行緒跑的佇列,這個佇列可以控制生產者的執行是並行還是序列
@property(strong,nonatomic)dispatch_queue_t producerQueue;


//消費者執行緒跑的佇列,這個佇列可以控制消費者的執行是並行還是序列
@property(strong,nonatomic) dispatch_queue_t consumerQueue;




-(void) initObj;


@end

NS_ASSUME_NONNULL_END

.m

//
//  ProComsumerModel.m
//  MultiThread
//
//  Created by liuxiaobing on 2018/10/31.
//  Copyright © 2018 liuxiaobing. All rights reserved.
//

#import "ProComsumerModel.h"
#import "ProComsumerModel.h"

@interface ProComsumerModel ()

@end

@implementation ProComsumerModel

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self initObj];
    
    [self initView];
    
}

-(void) initView{
    UIButton* button2 = [UIButton buttonWithType:UIButtonTypeCustom];
    [button2 setTitle:@"生產" forState:UIControlStateNormal];
    button2.frame = CGRectMake(20, 80, 200, 40);
    button2.backgroundColor = [UIColor brownColor];
    [button2 addTarget:self action:@selector(producer:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button2];
    
    
    UIButton* button3 = [UIButton buttonWithType:UIButtonTypeCustom];
    [button3 setTitle:@"消費" forState:UIControlStateNormal];
    button3.frame = CGRectMake(20, 180, 200, 40);
    button3.backgroundColor = [UIColor brownColor];
    [button3 addTarget:self action:@selector(comsumer:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button3];
}

-(void)producer:(UIButton*) button{
    [self producter];
}

-(void)comsumer:(UIButton*) button{
    [self comsumer];
}

-(void) initObj{
    
    self.baseroom = [[BaseRoom alloc] init];
    
    
    //分別建立N個生產者和M消費者各自的執行併發佇列
    //均使用併發佇列,即生產者之間可以併發執行,消費者之間也可以併發執行
    self.producerQueue = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);
    self.consumerQueue = dispatch_queue_create("consumer", DISPATCH_QUEUE_CONCURRENT);
    
}

-(void) producter{
    
//    //建立10個生產者持續生產皮鞋
//    for (int i = 0; i < 1; i++) {
//
//    }
    
    dispatch_async(self.producerQueue,  ^{
        while(1){
            NSString* t = [NSString stringWithFormat:@"nike"];
            [self.baseroom produce:t];
            sleep(1);
        }
        
    });
    
    
}

-(void)comsumer{
    
    dispatch_async(self.consumerQueue, ^{
        while(1){
            [self.baseroom comsumer];
            sleep(2);
        }
        
    });
    
    
}



/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

測試效果:

工程原始碼