OC 多執行緒GCD

韋家冰發表於2017-12-13

參考: GCD原始碼 深入理解 GCD iOS多執行緒--徹底學會多執行緒之『GCD』 關於iOS多執行緒,我說,你聽,沒準你就懂了

####任務執行方式

  • 同步執行(dispatch_sync):只能在當前執行緒中執行任務,不具備開啟新執行緒的能力。必須等到Block函式執行完畢後,dispatch函式才會返回。
  • 非同步執行(dispatch_async):可以在新的執行緒中執行任務,具備開啟新執行緒的能力。dispatch函式會立即返回, 然後Block在後臺非同步執行。

####任務管理方式

  • 序列佇列:所有任務會在一條執行緒中執行(有可能是當前執行緒也有可能是新開闢的執行緒),並且一個任務執行完畢後,才開始執行下一個任務。(等待完成)
  • 並行佇列:可以開啟多條執行緒並行執行任務(但不一定會開啟新的執行緒),並且當一個任務放到指定執行緒開始執行時,下一個任務就可以開始執行了。(等待發生)
    // 主佇列--序列,所有放在主佇列中的任務,都會放到主執行緒中執行
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 全域性佇列--並行,系統為我們建立好的一個並行佇列,使用起來與我們自己建立的並行佇列無本質差別
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    // new序列佇列
    dispatch_queue_t queue1 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    // new並行佇列
    dispatch_queue_t queue2 = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
複製程式碼

注意:避免使用 GCD Global佇列建立Runloop常駐執行緒 全域性佇列的底層是一個執行緒池,向全域性佇列中提交的 block,都會被放到這個執行緒池中執行,如果執行緒池已滿,後續再提交 block 就不會再重新建立執行緒。等待有空閒的執行緒在執行任務。 所以: 避免使用 GCD Global 佇列建立 Runloop 常駐執行緒,如果n條執行緒都被霸佔了,Global佇列就費了。

####任務+佇列

序列佇列 並行佇列 主佇列
同步(sync) 當前執行緒,序列執行 佇列當前執行緒,序列執行 主新執行緒,序列執行(注意死鎖)
非同步(async) 開1條新執行緒,序列執行 開n條新執行緒,非同步執行(n在iphone7上面最大是幾十個) 主新執行緒,序列執行

####Dispatch Block

佇列執行任務都是block的方式,

######建立block

- (void)createDispatchBlock {
    // 一般的block
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"run block");
    });
    dispatch_async(concurrentQueue, block);

    //QOS優先順序的block
    dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"run qos block");
    });
    dispatch_async(concurrentQueue, qosBlock);
}
複製程式碼

dispatch_block_wait:可以根據dispatch block來設定等待時間,引數DISPATCH_TIME_FOREVER會一直等待block結束

- (void)dispatchBlockWaitDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"star");
        [NSThread sleepForTimeInterval:5.f];
        NSLog(@"end");
    });
    dispatch_async(serialQueue, block);
    //設定DISPATCH_TIME_FOREVER會一直等到前面任務都完成
    dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
    NSLog(@"ok, now can go on");
}
複製程式碼

dispatch_block_notify:可以監視指定dispatch block結束,然後再加入一個block到佇列中。三個引數分別為,第一個是需要監視的block,第二個引數是需要提交執行的佇列,第三個是待加入到佇列中的block

- (void)dispatchBlockNotifyDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    //first block執行完才在serial queue中執行second block
    dispatch_block_notify(firstBlock, serialQueue, secondBlock);
}
複製程式碼

dispatch_block_cancel:iOS8之後可以呼叫dispatch_block_cancel來取消(需要注意必須用dispatch_block_create建立dispatch_block_t) 需要注意的是,未執行的可以用此方法cancel掉,若已經執行則cancel不了 如果想中斷(interrupt)執行緒,可以使用dispatch_block_testcancel方法

- (void)dispatchBlockCancelDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t firstBlock = dispatch_block_create(0, ^{
        NSLog(@"first block start");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"first block end");
    });
    dispatch_block_t secondBlock = dispatch_block_create(0, ^{
        NSLog(@"second block run");
    });
    dispatch_async(serialQueue, firstBlock);
    dispatch_async(serialQueue, secondBlock);
    //取消secondBlock
    dispatch_block_cancel(secondBlock);
}

複製程式碼

#####1. 序列佇列 + 同步執行 不會開啟新執行緒,在當前執行緒執行任務。任務是序列的,執行完一個任務,再執行下一個任務

- (void)serialQueueSync{
    
    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t serialQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
     
}
複製程式碼

#####2. 序列佇列 + 非同步執行 開一個新執行緒,一個一個執行任務

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x6000002616c0>{number = 1, name = main}
     4========<NSThread: 0x6000002616c0>{number = 1, name = main}
     1========<NSThread: 0x608000270540>{number = 3, name = (null)}
     2========<NSThread: 0x608000270540>{number = 3, name = (null)}
     3========<NSThread: 0x608000270540>{number = 3, name = (null)}
     */
複製程式碼

#####3. 並行佇列 + 同步執行 當前執行緒,一個一個執行任務

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     1========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     2========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     3========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     4========<NSThread: 0x60800007f840>{number = 3, name = (null)}
     */
複製程式碼

#####4. 並行佇列 + 非同步執行

開多個執行緒,非同步執行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x608000070300>{number = 1, name = main}
     4========<NSThread: 0x608000070300>{number = 1, name = main}
     2========<NSThread: 0x608000264140>{number = 4, name = (null)}
     1========<NSThread: 0x60000007a800>{number = 3, name = (null)}
     3========<NSThread: 0x6080002642c0>{number = 5, name = (null)}
     */
複製程式碼

#####5. 主佇列 + 非同步執行

主執行緒,同步執行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     4========<NSThread: 0x60000026e000>{number = 3, name = (null)}
     1========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     2========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     3========<NSThread: 0x60000007e2c0>{number = 1, name = main}
     */
複製程式碼

#####6. 主佇列 + 同步執行 (不能在主佇列這麼用,死鎖) 主執行緒,同步執行

    NSLog(@"0========%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
    
    /*
     NSLog輸出
     0========<NSThread: 0x600000263840>{number = 3, name = (null)}
     1========<NSThread: 0x608000078ec0>{number = 1, name = main}
     2========<NSThread: 0x608000078ec0>{number = 1, name = main}
     3========<NSThread: 0x608000078ec0>{number = 1, name = main}
     4========<NSThread: 0x600000263840>{number = 3, name = (null)}
     */
複製程式碼

####GCD其他用法 #####dispatch_after延時 1、time = 0,是直接呼叫非同步dispatch_async 2、time > 0, 只是延時提交block,不是延時立刻執行。

    //2秒延時、在主佇列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        
    });
複製程式碼

#####dispatch_once與dispatch_once_t

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //
    });
複製程式碼

1、dispatch_once並不是簡單的只執行一次那麼簡單 2、dispatch_once本質上可以接受多次請求,會對此維護一個請求連結串列 3、如果在block執行期間,多次進入呼叫同類的dispatch_once函式(即單例函式),會導致整體連結串列無限增長,造成永久性死鎖。

遞迴互相巢狀,如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ // 連結串列無限增長
        [self viewDidLoad];
    });
}
複製程式碼

dispatch_once原始碼

static void
dispatch_once_f_slow(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
#if DISPATCH_GATE_USE_FOR_DISPATCH_ONCE
	dispatch_once_gate_t l = (dispatch_once_gate_t)val;

	if (_dispatch_once_gate_tryenter(l)) {
		_dispatch_client_callout(ctxt, func);
		_dispatch_once_gate_broadcast(l);
	} else {
		_dispatch_once_gate_wait(l);
	}
#else
	_dispatch_once_waiter_t volatile *vval = (_dispatch_once_waiter_t*)val;
	struct _dispatch_once_waiter_s dow = { };
	_dispatch_once_waiter_t tail = &dow, next, tmp;
	dispatch_thread_event_t event;


	if (os_atomic_cmpxchg(vval, NULL, tail, acquire)) {
		
		// 第一次dispatch_once,原子性操作
		
		// 當前執行緒
		dow.dow_thread = _dispatch_tid_self();
		// 執行block
		_dispatch_client_callout(ctxt, func);

		// 第一次執行完了,設定token = DISPATCH_ONCE_DONE
		next = (_dispatch_once_waiter_t)_dispatch_once_xchg_done(val);
		while (next != tail) {
			
			// 繼續去下一個
			tmp = (_dispatch_once_waiter_t)_dispatch_wait_until(next->dow_next);
			event = &next->dow_event;
			next = tmp;
			
			// 訊號量++
			_dispatch_thread_event_signal(event);
		}
	} else {
		
		// 第二次dispatch_once進來
		_dispatch_thread_event_init(&dow.dow_event);
		next = *vval;
		for (;;) {
			if (next == DISPATCH_ONCE_DONE) { // token是否等於DISPATCH_ONCE_DONE
				// 第一次執行完之後,都是走這裡
				break;
			}
			// 如果是巢狀使用,第一次沒有完成,又要執行一次
			if (os_atomic_cmpxchgv(vval, next, tail, &next, release)) {
				// 原子性
				dow.dow_thread = next->dow_thread;
				dow.dow_next = next;
				if (dow.dow_thread) {
					pthread_priority_t pp = _dispatch_get_priority();
					_dispatch_thread_override_start(dow.dow_thread, pp, val);
				}
				// 等待訊號量
				_dispatch_thread_event_wait(&dow.dow_event);
				if (dow.dow_thread) {
					_dispatch_thread_override_end(dow.dow_thread, val);
				}
				break;
			}
		}
		_dispatch_thread_event_destroy(&dow.dow_event);
	}
#endif
}
複製程式碼

#####dispatch_apply(count,queue,block(index))迭代方法 該函式按指定的次數將指定的block追加到指定的佇列;使用的地方,阻塞當前執行緒

    NSLog(@"CurrentThread------%@", [NSThread currentThread]);
    //dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    // 6是次數
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd------%@",index, [NSThread currentThread]);
    });
    /*
     併發佇列:開多執行緒非同步執行
     NSLogx資訊
     CurrentThread------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     0------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     1------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     2------<NSThread: 0x608000266f00>{number = 5, name = (null)}
     3------<NSThread: 0x608000266f40>{number = 6, name = (null)}
     4------<NSThread: 0x600000268a40>{number = 3, name = (null)}
     5------<NSThread: 0x608000266e40>{number = 4, name = (null)}
     */
    
    
    /*
     同步佇列:當前執行緒同步執行
     NSLogx資訊
     CurrentThread------<NSThread: 0x608000072c00>{number = 3, name = (null)}
     0------<NSThread: 0x6000000694c0>{number = 1, name = main}
     1------<NSThread: 0x6000000694c0>{number = 1, name = main}
     2------<NSThread: 0x6000000694c0>{number = 1, name = main}
     3------<NSThread: 0x6000000694c0>{number = 1, name = main}
     4------<NSThread: 0x6000000694c0>{number = 1, name = main}
     5------<NSThread: 0x6000000694c0>{number = 1, name = main}
     */
複製程式碼

dispatch_apply能避免執行緒爆炸,因為GCD會管理併發

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 999; i++){
      dispatch_async(queue, ^{
         NSLog(@"%d,%@",i,[NSThread currentThread]);// 能開多大執行緒就開多大執行緒(幾十個)
      });
}
複製程式碼
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(999, queue, ^(size_t i){
     NSLog(@"%d,%@",i,[NSThread currentThread]); // 只開一定數量的執行緒(幾個)
});
複製程式碼

#####dispatch_suspend、dispatch_resume (用在dispatch_get_global_queue主佇列無效)

dispatch_suspend,dispatch_resume提供了“掛起、恢復”佇列的功能,簡單來說,就是可以暫停、恢復佇列上的任務。但是這裡的“掛起”,並不能保證可以立即停止佇列上正在執行的block

注意點: ######1、如果佇列沒有使用dispatch_suspend,使用dispatch_resume會crash

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    dispatch_resume(queue); // crash
}
複製程式碼

######2、如果queue被掛起,queue銷燬時候沒有被喚醒,會crash

        dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);// 如果queue被掛起,queue銷燬時候沒有被喚醒,會crash
複製程式碼

######3、dispatch_suspend後面執行dispatch_sync,阻塞當前執行緒,需要其他執行緒恢復佇列

        queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
        dispatch_suspend(queue);
        // 後面執行dispatch_sync,阻塞當前執行緒,需要其他執行緒恢復佇列
        dispatch_sync(queue, ^{
            NSLog(@"=====%@",@"111111");
        });
        NSLog(@"=====%@",@"22222");
複製程式碼

####GCD的佇列組dispatch_group_t,其實就是封裝了一個無限大的訊號量, 注意事項 1、dispatch_group_async(只有async,無sync)等價於{dispatch_group_enter() + async}, async呼叫完了會執行dispatch_group_leave()。 2、dispatch_group_enter()就是訊號量--; 3、dispatch_group_leave()就是訊號量++ 4、dispatch_group_enter() 必須執行在 dispatch_group_leave() 之前。 5、dispatch_group_enter() 和 dispatch_group_leave() 需要成對出現的

    //1.建立佇列組
    dispatch_group_t group = dispatch_group_create();

    //2.1.全域性佇列
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    
    //2.2.主佇列
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
   
    //2.3.自建序列佇列
    dispatch_group_async(group, dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL), ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    
    //3.都完成後會自動通知,不阻塞當前執行緒
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"當前執行緒=====%@",[NSThread currentThread]);
    
     /*
      並行佇列、自建序列佇列的任務多執行緒非同步執行
      主佇列的任務主執行緒同步執行,且排在全部任務的最後
      
      NSLog資訊
      當前執行緒=====<NSThread: 0x60000007c240>{number = 1, name = main}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-01 - <NSThread: 0x60800026d180>{number = 3, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-03 - <NSThread: 0x60000026c7c0>{number = 4, name = (null)}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      group-02 - <NSThread: 0x60000007c240>{number = 1, name = main}
      完成 - <NSThread: 0x60000007c240>{number = 1, name = main}
      */
複製程式碼

#####手動標記group完成

  • dispatch_group_enter(group)
  • dispatch_group_leave(group);

######dispatch_group_notify(不阻塞)想當與把block任務加在最後

NSLog(@"start");
    //1.建立佇列組
    dispatch_group_t group = dispatch_group_create();
    for (int i=0; i< 5; i++) {
        dispatch_group_enter(group); // enter
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // something
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
    }
    // 都完成後會自動通知,不阻塞當前執行緒
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    NSLog(@"end");

     /*
      NSLog資訊:
      start
      end
      something===1
      something===0
      something===2
      something===3
      something===4
      完成 - <NSThread: 0x60800006e900>{number = 1, name = main}
      */
    

複製程式碼

######dispatch_group_wait就是等待group的訊號量回到初始值(阻塞當前執行緒)

    NSLog(@"start");
    //1.建立佇列組
    dispatch_group_t group = dispatch_group_create();
    
    for (int i=0; i< 5; i++) {
        
        dispatch_group_enter(group); // enter
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            // something
            
            [NSThread sleepForTimeInterval:2];
            NSLog(@"something===%zd",i);
            dispatch_group_leave(group); // eave
        });
        
    }
    // 阻塞當前執行緒的、等待5秒
    dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
    dispatch_group_wait(group, waitTime);//特殊值有:DISPATCH_TIME_FOREVER是無限等,DISPATCH_TIME_NOW是不等
    NSLog(@"end");
    
    
     /*
      等待時間 < 執行需要時間
      NSLog資訊:
      start
      end
      something===0
      something===1
      something===3
      something===2
      something===4
      */
    
    /*
     等待時間 > 執行需要時間
     NSLog資訊:
     start
     something===1
     something===0
     something===4
     something===2
     something===3
     end
     */
複製程式碼

#####dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait 訊號量是控制任務執行的重要條件,當訊號量為0時,所有任務等待,訊號量越大,允許可並行執行的任務數量越多。

  • dispatch_semaphore_create(long value);建立訊號量,初始值不能小於0;value訊號數值
  • dispatch_semaphore_wait(semaphore, timeout);等待降低訊號量,也就是訊號量-1;timeout不是呼叫dispatch_semaphore_wait後等待的時間,而是訊號量建立後的時間
  • dispatch_semaphore_signal(semaphore);提高訊號量,也就是訊號量+1;
  • dispatch_semaphore_wait和dispatch_semaphore_signal通常配對使用。
    // 相當於控制新建的執行緒數
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i=0; i< 10; i++) {
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            [NSThread sleepForTimeInterval:1];
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
        
    }
    
    
     /*
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
      只有5條執行緒
      NSLog資訊:
      第0次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第1次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第3次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第2次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第4次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      第8次_<NSThread: 0x608000268ac0>{number = 3, name = (null)}
      第7次_<NSThread: 0x600000269100>{number = 5, name = (null)}
      第6次_<NSThread: 0x608000268b00>{number = 4, name = (null)}
      第5次_<NSThread: 0x600000269240>{number = 6, name = (null)}
      第9次_<NSThread: 0x600000269780>{number = 7, name = (null)}
      */
    
    /*
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
     10條執行緒
     NSLog資訊:
     第2次_<NSThread: 0x6080000661c0>{number = 3, name = (null)}
     第4次_<NSThread: 0x608000073e40>{number = 7, name = (null)}
     第1次_<NSThread: 0x600000079dc0>{number = 4, name = (null)}
     第5次_<NSThread: 0x6000000721c0>{number = 8, name = (null)}
     第3次_<NSThread: 0x608000073dc0>{number = 6, name = (null)}
     第0次_<NSThread: 0x608000073d40>{number = 5, name = (null)}
     第6次_<NSThread: 0x608000073d80>{number = 9, name = (null)}
     第9次_<NSThread: 0x608000073e00>{number = 10, name = (null)}
     第7次_<NSThread: 0x6000000717c0>{number = 11, name = (null)}
     第8次_<NSThread: 0x600000066b40>{number = 12, name = (null)}
     */
複製程式碼

#####dispatch_barrier_async、dispatch_barrier_sync (承上啟下--用於自建的並行佇列) 保證此前的任務都先於自己執行,此後的任務也遲於自己執行。 dispatch_barrier_async 不阻塞當前執行緒; dispatch_barrier_sync 阻塞當前執行緒;

注意:dispatch_barrier_(a)sync只在自己建立的併發佇列上有效,在全域性(Global)併發佇列、序列佇列上,效果跟dispatch_(a)sync效果一樣。

- (void)test{

    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
        NSLog(@"任務1");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務2");
    });
    dispatch_barrier_async(globalQueue, ^{
        NSLog(@"任務barrier");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務3");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"任務4");
    });
    /*
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431532] 任務2
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431535] 任務1
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431533] 任務barrier
     2017-09-02 21:03:40.255 NSThreadTest[28856:21431551] 任務3
     2017-09-02 21:03:40.256 NSThreadTest[28856:21431550] 任務4
     */
}
複製程式碼

GCD建立Timer

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //建立佇列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //1.建立一個GCD定時器
    /*
     第一個引數:表明建立的是一個定時器
     第四個引數:佇列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 需要對timer進行強引用,保證其不會被釋放掉,才會按時呼叫block塊
    // 區域性變數,讓指標強引用
    self.timer = timer;
    //2.設定定時器的開始時間,間隔時間,精準度
    /*
     第1個引數:要給哪個定時器設定
     第2個引數:開始時間
     第3個引數:間隔時間
     第4個引數:精準度 一般為0 在允許範圍內增加誤差可提高程式的效能
     GCD的單位是納秒 所以要*NSEC_PER_SEC
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //3.設定定時器要執行的事情
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"---%@--",[NSThread currentThread]);
    });
    // 啟動
    dispatch_resume(timer);
}

複製程式碼

#####GCD各種死鎖的情況

######1、最常見的

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"0========%@",[NSThread currentThread]);// 當前是主執行緒、主佇列
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    NSLog(@"2========%@",[NSThread currentThread]);
    
    /*
     NSLog資訊
     0========<NSThread: 0x60000007c5c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主佇列,dispatch_sync也屬於主佇列,
     2.dispatch_sync是viewDidLoad裡面的程式碼,viewDidLoad需要等待dispatch_sync執行完,dispatch_sync需要等待viewDidLoad執行完,這就死鎖了
     */
    
}
複製程式碼
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"0========%@",[NSThread currentThread]);// 當前是主執行緒、主佇列
    // 改成dispatch_get_global_queue或者new出來的佇列
    dispatch_sync(dispatch_get_global_queue(0,0), ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    NSLog(@"2========%@",[NSThread currentThread]);
    
    /*
     NSLog資訊
     0========<NSThread: 0x6000000690c0>{number = 1, name = main}
     1========<NSThread: 0x6000000690c0>{number = 1, name = main}
     2========<NSThread: 0x6000000690c0>{number = 1, name = main}
     
     解析
     1.viewDidLoad是主佇列,dispatch_sync是global_queue,不在同一佇列
     2.dispatch_sync是viewDidLoad裡面的程式碼,viewDidLoad需要等待dispatch_sync執行完返回,但是dispatch_sync不需要等待viewDidLoad執行完,立即執行完返回
     */
    
}
複製程式碼

######2、序列佇列,各種巢狀非同步情況 死鎖的原因:是同一個序列佇列任務內部程式碼繼續巢狀同步sync的任務

        // 序列佇列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
    
    // 同步嵌非同步----執行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 非同步嵌非同步----執行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 非同步嵌同步----死鎖
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----死鎖
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });

複製程式碼

######3、並行佇列,各種巢狀非同步情況 並行佇列佇列各種巢狀都不會死鎖

    // 並行佇列
    dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步嵌非同步----執行OK
    dispatch_sync(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 非同步嵌非同步----執行OK
    dispatch_async(queue, ^{
        dispatch_async(queue, ^{
        });
    });
    
    // 非同步嵌同步----執行OK
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
    
    // 同步嵌同步----執行OK
    dispatch_sync(queue, ^{
        dispatch_sync(queue, ^{
        });
    });
複製程式碼

######4、dispatch_apply阻塞當前執行緒

// 主佇列使用,死鎖
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
複製程式碼
// 巢狀使用,死鎖
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任務
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任務
        ...
    });
});
複製程式碼

dispatch_barrier dispatch_barrier_sync在序列佇列和全域性並行佇列裡面和dispatch_sync同樣的效果,所以需考慮同dispatch_sync一樣的死鎖問題。

######5、 訊號量阻塞主執行緒

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"semaphore create!");
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(semaphore);
        NSLog(@"semaphore plus 1");
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore minus 1");
}
複製程式碼

原因: 如果當前執行的執行緒是主執行緒,以上程式碼就會出現死鎖。 因為dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了當前執行緒,而且等待時間是DISPATCH_TIME_FOREVER——永遠等待,這樣它就永遠的阻塞了當前執行緒——主執行緒。導致主線中的dispatch_semaphore_signal(semaphore)沒有執行, 而dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改變訊號量,這樣就形成了死鎖。

解決方法: 應該將訊號量移到並行佇列中,如全域性排程佇列。以下場景,移到序列佇列也是可以的。但是序列佇列還是有可能死鎖的(如果執行dispatch_semaphore_signal方法還是在對應序列佇列中的話,即之前提到的序列佇列巢狀序列佇列的場景)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSLog(@"semaphore create!");
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(semaphore); // +1
            NSLog(@"semaphore plus 1");
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore minus 1");
    });
}
複製程式碼

#####一些巢狀使用問題

    NSLog(@"1");
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_async(dispatch_get_main_queue(), ^{
		   sleep(1);
            NSLog(@"3");
        });
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"4");
    });
    NSLog(@"5");

    //結果是:12534
    //解析:“2”併發佇列同步任務,所以125;“3”、“4”是兩個主佇列非同步,序列執行任務34;最終就是12534
複製程式碼
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        
        dispatch_sync(queue, ^{
            NSLog(@"B");
        });
        NSLog(@"A");
    });
    // 結果是:BA (併發佇列不會死鎖) 並行佇列,任務A加入佇列執行中,然後任務B加入佇列也立即執行,但是任務A會等任務B先執行完。
複製程式碼

相關文章