ios--dispatch_semaphore

當我們在處理一系列線程的時候,當數量達到一定量,在以前我們可能會選擇使用NSOperationQueue來處理併發控制,但如何在GCD中快速的控制併發呢?答案就是dispatch_semaphore,對經常做unix開發的人來講,我所介紹的內容可能就顯得非常入門級了,信號量在他們的多線程開發中再平常不過了。
  信號量是一個整形值並且具有一個初始計數值,並且支持兩個操作:信號通知和等待。當一個信號量被信號通知,其計數會被增加。當一個線程在一個信號量上等待時,線程會被阻塞(如果有必要的話),直至計數器大於零,然後線程會減少這個計數。
  在GCD中有三個函數是semaphore的操作,分別是:
  dispatch_semaphore_create   創建一個semaphore
  dispatch_semaphore_signal   發送一個信號
  dispatch_semaphore_wait    等待信號

  簡單的介紹一下這三個函數,第一個函數有一個整形的參數,我們可以理解爲信號的總量,dispatch_semaphore_signal是發送一個信號,自然會讓信號總量加1,dispatch_semaphore_wait等待信號,當信號總量少於0的時候就會一直等待,否則就可以正常的執行,並讓信號總量-1,根據這樣的原理,我們便可以快速的創建一個併發控制來同步任務和有限資源訪問控制



  int data = 3;

    __block int mainData = 0;

    __block dispatch_semaphore_t sem = dispatch_semaphore_create(0);

    

    dispatch_queue_t queue = dispatch_queue_create("StudyBlocks", NULL);

    

    dispatch_async(queue, ^(void) {

        int sum = 0;

        for(int i = 0; i < 5; i++)

        {

            sum += data;

            

            NSLog(@" >> Sum: %d", sum);

        }

        

        dispatch_semaphore_signal(sem);

    });

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    for(int j=0;j<5;j++)

    {

        mainData++;

        NSLog(@">> Main Data: %d",mainData);

    }

    dispatch_release(sem);

    dispatch_release(queue);



輸出:

2013-07-08 11:33:05.654 dispatch[1102:1e03]  >> Sum: 3

2013-07-08 11:33:05.656 dispatch[1102:1e03]  >> Sum: 6

2013-07-08 11:33:05.657 dispatch[1102:1e03]  >> Sum: 9

2013-07-08 11:33:05.658 dispatch[1102:1e03]  >> Sum: 12

2013-07-08 11:33:05.659 dispatch[1102:1e03]  >> Sum: 15

2013-07-08 11:33:05.660 dispatch[1102:c07] >> Main Data: 1

2013-07-08 11:33:05.660 dispatch[1102:c07] >> Main Data: 2

2013-07-08 11:33:05.660 dispatch[1102:c07] >> Main Data: 3

2013-07-08 11:33:05.661 dispatch[1102:c07] >> Main Data: 4

2013-07-08 11:33:05.661 dispatch[1102:c07] >> Main Data: 5

通過信號量就可以保證,Main Data 永遠在Sum之後執行


GCD多線程下,實現線程同步的方式有如下幾種:

1.串行隊列 2.並行隊列 3.分組 4.信號量

實例: 去網上獲取一張圖片並展示在視圖上. 實現這個需求,可以拆分成兩個任務,一個是去網上獲取圖片,一個是展示在視圖上. 這兩個任務是有關聯的,所以需要同步處理.

下面看這幾種方式如何實現.

 

一、

1.串行隊列

1.1[GCD相關:]

(1)GCD下的dispatch_queue隊列都是FIFO隊列,都會按照提交到隊列的順序執行.

只是根據隊列的性質,分爲<1>串行隊列:用戶隊列、主線程隊列 <2>並行隊列. 

(2)同步(dispatch_sync)、異步方式(dispatch_async). 配合串行隊列和並行隊列使用.

1.2同步隊列直接提交兩個任務就可以.

複製代碼
    // 串形隊列
    dispatch_queue_t serilQueue = dispatch_queue_create("com.quains.myQueue", 0);
    
    //開始時間
    NSDate *startTime = [NSDate date];
    
    
    __block UIImage *image = nil;
    
    //1.先去網上下載圖片
    dispatch_async(serilQueue, ^{
        NSString *urlAsString = @"http://avatar.csdn.net/B/2/2/1_u010013695.jpg";
        NSURL *url = [NSURL URLWithString:urlAsString];
        
        NSError *downloadError = nil;
        
        NSData *imageData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:&downloadError];
        
        if (downloadError == nil && imageData != nil) {
            image = [[UIImage imageWithData:imageData] retain];
        }
        else if(downloadError != nil){
            NSLog(@"error happened = %@", downloadError);
        }
        else{
            NSLog(@"No data download");
        }
    });
    
    //2.在主線程展示到界面裏
    dispatch_async(serilQueue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        // 在主線程展示
        dispatch_async(dispatch_get_main_queue(), ^{
        if (image != nil) {
            
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
            
            [imageView setImage:image];
            
            [imageView setContentMode:UIViewContentModeScaleAspectFit];
            [self.view addSubview:imageView];
            [imageView release];
            
            NSDate *endTime = [NSDate date];
            NSLog(@"串行異步 completed in %f time", [endTime timeIntervalSinceDate:startTime]);
        }
        else{
            NSLog(@"image isn't downloaded, nothing to display");
        }
        });
        
    });
    
    //3.清理
    dispatch_release(serilQueue);
    [image release];
複製代碼

注意:

(1) __block變量分配在棧,retain下,防止被回收.

(2)dispatch要手動create和release.

(3)提交到主線程隊列的時候,慎用同步dispatch_sync方法,有可能造成死鎖. 因爲主線程隊列是串行隊列,要等隊列裏的任務一個一個執行.所以提交一個任務到隊列,如果用同步方法就會阻塞住主線程,而主線程又要等主線程隊列裏的任務都執行完才能執行那個剛提交的,所以主線程隊列裏還有其他的任務的話,但他已經被阻塞住了,沒法先完成隊列裏的其他任務,即,最後一個任務也沒機會執行到,於是造成死鎖.

(4)提交到串行隊列可以用同步方式,也可以用異步方式.

 

2.並行隊列

採用並行隊列的時候,可以採用同步的方式把任務提交到隊列裏去,即可以實現同步的方式

複製代碼
//新建一個隊列
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //記時
    NSDate *startTime = [NSDate date];
    
    //加入隊列
    dispatch_async(concurrentQueue, ^{
        __block UIImage *image = nil;
        
        //1.先去網上下載圖片
        dispatch_sync(concurrentQueue, ^{
            NSString *urlAsString = @"http://avatar.csdn.net/B/2/2/1_u010013695.jpg";
            NSURL *url = [NSURL URLWithString:urlAsString];
            
            NSError *downloadError = nil;
            
            NSData *imageData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:&downloadError];
            
            if (downloadError == nil && imageData != nil) {
                image = [UIImage imageWithData:imageData];
            }
            else if(downloadError != nil){
                NSLog(@"error happened = %@", downloadError);
            }
            else{
                NSLog(@"No data download");
            }
        });
        
        //2.在主線程展示到界面裏
        dispatch_sync(dispatch_get_main_queue(), ^{
            if (image != nil) {
                UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
                [imageView setImage:image];
                
                [imageView setContentMode:UIViewContentModeScaleAspectFit];
                [self.view addSubview:imageView];
                [imageView release];
                
                NSDate *endTime = [NSDate date];
                NSLog(@"並行同步 completed in %f time", [endTime timeIntervalSinceDate:startTime]);
            }
            else{
                NSLog(@"image isn't downloaded, nothing to display");
            }
        });
    });
複製代碼

兩個同步的任務用一個異步的包起來,提交到並行隊列裏去,即可實現同步的方式.

 

3.使用分組方式

3.1 group本身是將幾個有關聯的任務組合起來,然後提供給開發者一個知道這個group結束的點.

雖然這個只有一個任務,但是可以利用group的結束點,去阻塞線程,從而來實現同步方式.

複製代碼
dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    NSDate *startTime = [NSDate date];
    
    __block UIImage *image = nil;
    
    dispatch_group_async(group, queue, ^{
        
        //1.先去網上下載圖片
            NSString *urlAsString = @"http://avatar.csdn.net/B/2/2/1_u010013695.jpg";
            NSURL *url = [NSURL URLWithString:urlAsString];
            
            NSError *downloadError = nil;
            
            NSData *imageData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:&downloadError];
            
            if (downloadError == nil && imageData != nil) {
                image = [[UIImage imageWithData:imageData] retain];
            }
            else if(downloadError != nil){
                NSLog(@"error happened = %@", downloadError);
            }
            else{
                NSLog(@"No data download");
            }
        
        });
    
    // 2.等下載好了再在刷新主線程
    dispatch_group_notify(group, queue, ^{
        
        //在主線程展示到界面裏
        dispatch_async(dispatch_get_main_queue(), ^{
            if (image != nil) {
                UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
                [imageView setImage:image];
                [image release];
                
                [imageView setContentMode:UIViewContentModeScaleAspectFit];
                [self.view addSubview:imageView];
                [imageView release];
                
                NSDate *endTime = [NSDate date];
                NSLog(@"分組同步 completed in %f time", [endTime timeIntervalSinceDate:startTime]);
            }
            else{
                NSLog(@"image isn't downloaded, nothing to display");
            }
        });
        
    });
    
    // 釋放掉
    dispatch_release(group);
複製代碼

dispatch_group 也要手動創建和釋放.

dispatch_notify()提供了一個知道group什麼時候結束的點. 當然也可以使用dispatch_wait()去阻塞.

 

4.信號量

信號量 和 瑣 的作用差不多,可以用來實現同步的方式. 

但是信號量通常用在 允許幾個線程同時訪問一個資源,通過信號量來控制訪問的線程個數.

複製代碼
// 信號量初始化爲1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    NSDate *startTime = [NSDate date];
    
    __block UIImage *image = nil;
    
    
    //1.先去網上下載圖片
    dispatch_async(queue, ^{
        
        // wait操作-1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        // 開始下載
        NSString *urlAsString = @"http://avatar.csdn.net/B/2/2/1_u010013695.jpg";
        NSURL *url = [NSURL URLWithString:urlAsString];
        
        NSError *downloadError = nil;
        
        NSData *imageData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:&downloadError];
        
        if (downloadError == nil && imageData != nil) {

            image = [[UIImage imageWithData:imageData] retain];
            //NSLog(@"heap %@", image);
            //NSLog(@"%d",[image retainCount]);
        }
        else if(downloadError != nil){
            NSLog(@"error happened = %@", downloadError);
        }
        else{
            NSLog(@"No data download");
        }

        // signal操作+1
        dispatch_semaphore_signal(semaphore);
    });
    
  
    // 2.等下載好了再在刷新主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        
        // wait操作-1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        if (image != nil) {
            
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
            
            [imageView setImage:image];
            NSLog(@"%d",[image retainCount]);
            [image release];
            
            [imageView setContentMode:UIViewContentModeScaleAspectFit];
            [self.view addSubview:imageView];
            [imageView release];
            
            NSDate *endTime = [NSDate date];
            NSLog(@"信號量同步 completed in %f time", [endTime timeIntervalSinceDate:startTime]);
        }
        else{
            NSLog(@"image isn't downloaded, nothing to display");
        }
        
        // signal操作+1
        dispatch_semaphore_signal(semaphore);
    });
複製代碼

dispatch_wait會阻塞線程並且檢測信號量的值,直到信號量值大於0纔會開始往下執行,同時對信號量執行-1操作.

dispatch_signal則是+1操作.

 

二、

以上幾種方式,都是通過阻塞線程的方式去實現同步。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章