筆記-多線程底層再探

函數與隊列

把任務添加到隊列,而且指定執行任務的函數面試

  • 任務使用block封裝,且任務的block沒有參數也沒有返回值
  • 執行任務的函數
    • 異步 dispatch_async{}
      • 不用等待當前語句執行完畢,就能夠執行下一條語句
      • 會開啓線程執行block的任務
      • 異步是多線程的代名詞
    • 同步 dispatch_sync{}
      • 必須等待當前語句執行完畢,纔會執行下一條語句
      • 不會開啓線程
      • 在當前執行block的任務 還原最基本的寫法:
// 把任務添加到隊列 --> 函數
    // 任務 
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    //串行隊列
    dispatch_queue_t queue = dispatch_queue_create("com.zb.cn", NULL);
    // 函數
    dispatch_async(queue, block);
複製代碼

隊列: 數組

特殊的兩種隊列:安全

主隊列 dispatch_get_main_queue()bash

  • 專門用來在主線程上調度任務的隊列,是串行隊列
  • 不會開啓線程
  • 若是當前主線程正在有任務執行,那麼不管主隊列中當前被添加了什麼任務,都不會被調度

全局隊列 dispatch_get_global_queue()多線程

  • 全局隊列是一個併發隊列
  • 在使用多線程開發時,若是對隊列沒有特殊需求,在執行異步任務時,能夠直接使用全局隊列

隊列與函數:併發

理解上面幾種組合後,嘗試解答出下面的任務輸出順序、、異步

問題一
- (void)textOne {
    dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"任務1");
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_async(queue, ^{
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

首先明確是一個併發隊列,裏面有任務1dispatch_asyncblock任務、任務5,因此按順序輸出任務1任務5;裏面嵌套的異步操做和外面的分析如出一轍,即整個的輸出順序爲一、五、二、四、3async

問題二
- (void)textTwo {
    dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_SERIAL);
    NSLog(@"任務1");
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_sync(queue, ^{
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
}
複製代碼

這裏是一個串行隊列,dispatch_async任務開啓了一個線程專門處理,沒必要等待,因此先按順序輸出任務1任務5;進入第一個dispatch_async任務,串行隊列,因此也是按順序執行任務2dispatch_asyncblock任務、任務4;此時的block任務是一個同步函數,因此當任務2執行完畢之後,走到這個發現是同步,而後就把任務3加入到隊列裏執行,此時隊列裏的任務是任務2dispatch_asyncblock任務、任務4任務3;根據 FIFO 原則正常行走,任務2結束後,執行dispatch_asyncblock任務,可是由於同步的緣由,執行這個block任務又必需要執行任務3,執行任務3的前提是任務4執行結束,執行任務4的前提是block任務執行結束,這裏發生裏死鎖。因此任務的輸出順序爲任務1任務5任務2,而後奔潰。函數

死鎖的產生url

  • 主線程由於同步函數的緣由等着先執行任務
  • 主隊列等着主線程的任務執行完畢在執行本身的任務
  • 主隊列和主線程相互等待會形成死鎖

若是把上面的串行隊列改爲併發隊列,輸出的結果又是什麼樣的呢?

下面看一個面試題

- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 0; 
    while (a < 10) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            a ++;
        });
    }
    NSLog(@"%d",a);
}
複製代碼

問題一:a++ 報錯的緣由?
問題二:修改正確後,輸出a=?
問題三:在不改變函數以及隊列的前提下,如何讓a的輸出爲10?

答案一:__block 修飾a的初始化,把a的指針和值從棧區copystruct,堆區。
答案二:輸出結果a >= 10,在while循環裏,每一次的循環都會產生一個線程,執行異步操做,不等待直接執行後面的任務,同時這也是耗時操做,因此在循環裏可能會走不少次a++操做
答案三:能夠經過加鎖的方式,實現輸出a=10

具體代碼以下

- (void)viewDidLoad {
    [super viewDidLoad];
    __block int a = 0; 
    // 信號量
    dispatch_semaphore_t lock =  dispatch_semaphore_create(1);
    while (a < 10) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            a ++;
            dispatch_semaphore_signal(lock);
        });
        // 堵死
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    }
    NSLog(@"%d",a);
}
複製代碼

GCD的使用

柵欄函數 dispatch_barrier_sync

- (void)demo2{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    /* 1.異步函數 */
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    /* 2. 柵欄函數 */
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    NSLog(@"加載那麼多,喘口氣!!!");
    /* 3. 異步函數 */
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"平常處理3-%zd-%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"**********起來幹!!");
    
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"平常處理4-%zd-%@",i,[NSThread currentThread]);
        }
    });
}
複製代碼

這裏就達到了download1download2任務完成後,纔去執行平常處理3平常處理4任務的效果。

提問1:若是把併發隊列dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT); 改爲dispatch_get_global_queue(0,0);後效果會怎麼樣?

執行代碼後會神奇的發現,柵欄效果神奇的失效了

這裏須要注意了,柵欄函數必定要是自定義的併發隊列,否則就無效,分析一下也能夠得知,dispatch_get_global_queue是全局的併發隊列,加上柵欄實際上就是一個堵塞,若是有效的話,系統就。。GG了。

提問2:若是把download1download2任務的隊列換成一個其餘的隊列,效果會怎麼樣?

執行代碼後,也會發現,不在同一個隊列的話,柵欄也是無效,因此這裏也是一個須要注意的地方,必需要求都在同一個隊列

這是柵欄函數的第一個做用,保證順序執行

看下面代碼:

for (int i = 0; i < 5000; i++) {
    dispatch_async(concurrentQueue, ^{
        NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
        NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
}
複製代碼

執行後,會發生crash,異步函數,建立了多條線程,同時對數組執行addObject操做,形成資源搶奪,發送崩潰。

dispatch_queue_t concurrentQueue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5000; i++) {
        dispatch_async(concurrentQueue, ^{
            .
            .
            .
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }
複製代碼

執行後,能夠正常執行,輸出結果。
第二個做用,保證線程安全

調度組 group

建立組 dispatch_group_create
進組任務 dispatch_group_async
進組任務執行完畢通知: dispatch_group_notify
進組任務執行等待時間:dispatch_group_wait

進組 dispatch_group_enter
出組 dispatch_group_leave

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
    
dispatch_group_async(group, queue, ^{
    NSLog(@"任務1");
});
    
long timeOut = dispatch_group_wait(group, 0.5);
dispatch_group_notify(group, queue, ^{
    NSLog(@"任務2");
});

或

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"任務");
    dispatch_group_leave(group);
});
複製代碼

使用enterleave時,必定要成對出現,否則會產生crash。

信號量dispatch_semaphore_t

建立信號量 dispatch_semaphore_create
信號量等待 dispatch_semaphore_wait
信號量釋放 dispatch_semaphore_signal

能夠看成鎖來使用,在本文一開始就使用了,還能夠控制GCD最大併發數dispatch_semaphore_create(x)

相關文章
相關標籤/搜索