主頁 > 移動端開發 > iOS底層原理(七)多執行緒(上)

iOS底層原理(七)多執行緒(上)

2021-04-09 07:03:26 移動端開發

基本概念

行程和執行緒

  • 行程:行程是指在系統中正在運行的一個應用程式
  • 執行緒:1個行程要想執行任務,必須得有執行緒(每1個行程至少要有1條執行緒)
  • 一個行程(程式)的所有任務都在執行緒中執行
  • 1個執行緒中任務的執行是串行的

行程和執行緒的比較

  • 執行緒是CPU呼叫(執行任務)的最小單位
  • 行程是CPU分配資源和調度的單位
  • 一個程式可以對應多個行程,一個行程中可以有多個執行緒,但至少要有一個執行緒
  • 同一個行程內的執行緒共享行程的資源

多執行緒

  • 1個行程中可以開啟多條執行緒,每條執行緒可以并行(同時)執行不同的任務
  • 同一時間,CPU只能處理1條執行緒,只有1條執行緒在作業(執行)
  • 多執行緒并發(同時)執行,其實是CPU快速地在多條執行緒之間調度(切換)
  • 如果CPU調度執行緒的時間足夠快,就造成了多執行緒并發執行的假象
優點
  • 能適當提高程式的執行效率
  • 能適當提高資源利用率(CPU、記憶體利用率)
缺點
  • 創建執行緒是有開銷的
  • 如果開啟大量的執行緒,會降低程式的性能
  • 執行緒越多,CPU在調度執行緒上的開銷就越大
  • 程式設計更加復雜:比如執行緒之間的通信、多執行緒的資料共享

主執行緒

  • 一個iOS程式運行后,默認會開啟1條執行緒,稱為“主執行緒”或“UI執行緒”
  • 顯示\重繪UI界面
  • 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
  • 別將比較耗時的操作放到主執行緒中

iOS中的常見多執行緒方案

NSThread、GCD、NSOperation底層都是基于pthread來實作的

NSThread

判斷以及獲取執行緒的方法

// 1.獲得主執行緒
NSThread *mainThread = [NSThread mainThread];
 
// 2.獲得當前執行緒
NSThread *currentThread  = [NSThread currentThread];
 
// 3.判斷主執行緒
// 類方法
BOOL isMainThreadA = [NSThread isMainThread];
// 物件方法
BOOL isMainThreadB = [currentThread isMainThread];

創建執行緒的方法

// 1.手動啟動執行緒
// 可以拿到執行緒物件進行詳細設定
// object:需要傳遞的引數
NSThread *threadA = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ABC"];

// 設定屬性
threadA.name = @"執行緒A";
//設定優先級  取值范圍 0.0 ~ 1.0 之間 最高是1.0 默認優先級是0.5
threadA.threadPriority = 1.0;
    
// 啟動執行緒
[threadA start];


// 2.自動啟動執行緒
// 無法對執行緒進行更詳細的設定
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分離子執行緒"];


// 3.開啟一條后臺執行緒
[self performSelectorInBackground:@selector(run:) withObject:@"開啟后臺執行緒"];

其他常用方法

// 1.阻塞執行緒
[NSThread sleepForTimeInterval:2.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];

// 2.回到主執行緒
// waitUntilDone:是否需要等待
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

// 3.可以設定在哪個執行緒執行
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

// 4.退出執行緒
//注意:執行緒死了不能復生
[NSThread exit];

NSOperation

NSOperation的基本使用

// 1.創建操作
// alloc init 方式創建操作
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
// block 方式創建操作
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
   NSLog(@"1----%@",[NSThread currentThread]);
}];


// 2.追加任務
// 注意:如果一個操作中的任務數量大于1,那么會開子執行緒并發執行任務
// 注意:不一定是子執行緒,有可能是主執行緒
[op2 addExecutionBlock:^{
   NSLog(@"2---%@",[NSThread currentThread]);
}];
 
 
// 3.啟動
[op1 start];
[op2 start];


// 4.操作監聽
// 執行操作完畢后會執行該回呼
op2.completionBlock = ^{
   NSLog(@"%@",[NSThread currentThread]);
};


// 5.設定依賴
// 注意點:不能回圈依賴
// 可以跨佇列依賴
[op1 addDependency:op2];

佇列的基本使用

第一種創建方式

// 1.創建操作,封裝任務
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];

// 2.創建佇列
// 非主佇列: (同時具備并發和串行的功能)
// 默認情況下,非主佇列是并發佇列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 3.添加操作到佇列中
[queue addOperation:op1];   //內部已經呼叫了[op1 start]

第二種創建方式

// 1.創建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
   NSLog(@"1----%@",[NSThread currentThread]);   
}];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
   NSLog(@"2----%@",[NSThread currentThread]);   
}];

// 追加任務
[op2 addExecutionBlock:^{
   NSLog(@"3----%@",[NSThread currentThread]);
}];

// 2.創建佇列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
// 3.添加操作到佇列
[queue addOperation:op1];
[queue addOperation:op2];

第三種創建方式

// 1.創建佇列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

// 2.佇列直接添加一個操作(省略創建操作)
[queue addOperationWithBlock:^{
    NSLog(@"1----%@",[NSThread currentThread]);
}];

第四種創建方式

// 1.創建佇列
// LLOperation繼承自NSOperation
 LLOperation *op1 = [[LLOperation alloc]init];
 
// 2.LLOperation內部重寫 main 方法
- (void)main {
   NSLog(@"main---%@",[NSThread currentThread]);
} 

// 3.創建佇列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
// 4.添加操作到佇列
[queue addOperation:op1];

佇列的其他用法

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
   
// 1.設定最大并發數量 
/*
同一時間最多有多少個任務可以執行
串行執行任務!=只開一條執行緒 (執行緒同步)
maxConcurrentOperationCount >1 那么就是并發佇列
maxConcurrentOperationCount == 1 那就是串行佇列
maxConcurrentOperationCount == 0  不會執行任務
maxConcurrentOperationCount == -1 特殊意義 最大值 表示不受限制
*/
queue.maxConcurrentOperationCount = 5;


// 2.暫停(可以恢復)
// YES代表暫停佇列,NO代表恢復佇列
/*
佇列中的任務也是有狀態的:已經執行完畢的 | 正在執行 | 排隊等待狀態
不能暫停當前正在處于執行狀態的任務
*/
[queue setSuspended:YES];


// 3.取消(不可以恢復)
// 該方法內部呼叫了所有操作的cancel方法
[queue cancelAllOperations];


// 4.創建主佇列
// 會在主執行緒執行操作,不開執行緒
NSOperationQueue *queue = [NSOperationQueue mainQueue];

總結:

  • NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
  • 默認情況下,NSInvocationOperation呼叫了start方法后并不會開一條新執行緒去執行操作,而是在當前執行緒同步執行操作;
    只有將NSOperation放到一個NSOperationQueue中,才會異步執行操作
  • 只要NSBlockOperation封裝的運算元 > 1,就會異步執行操作
  • 操作之間不能相互依賴,會造成回圈依賴
  • 經常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出回應

GCD

GCD的佇列

GCD的佇列可以分為2大型別
  • 并發佇列(Concurrent Dispatch Queue)
    • 可以讓多個任務并發(同時)執行(自動開啟多個執行緒同時執行任務)
    • 并發功能只有在異步dispatch_async函式下才有效
  • 串行佇列(Serial Dispatch Queue)
    • 讓任務一個接著一個地執行(一個任務執行完畢后,再執行下一個任務)
// 1.創建佇列
/*
  第一個引數:C語言的字串,標簽
  第二個引數:佇列的型別
  DISPATCH_QUEUE_CONCURRENT:并發
  DISPATCH_QUEUE_SERIAL:串行
*/

// 并發佇列
dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_CONCURRENT);
// 串行佇列
dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_SERIAL);


// 2.獲得全域并發佇列
// 第一個引數:可以設定優先級
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


// 3.獲得主佇列
dispatch_queue_t queue = dispatch_get_main_queue();


// 4.異步函式
dispatch_async(queue, ^{
   NSLog(@"download1----%@",[NSThread currentThread]);
});
    
    
// 5.同步函式
 dispatch_sync(queue, ^{
   NSLog(@"download2----%@",[NSThread currentThread]);
});  
同步、異步、并發、串行的注意點:
  • 同步和異步主要影響:能不能開啟新的執行緒 - 同步:在當前執行緒中執行任務,不具備開啟新執行緒的能力 - 異步:在新的執行緒中執行任務,具備開啟新執行緒的能力- 并發和串行主要影響:任務的執行方式 - 并發:多個任務并發(同時)執行 - 串行:一個任務執行完畢后,再執行下一個任務

創建一個同步串行佇列

// 不論是哪種佇列,都不會開啟新執行緒
dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_main_queue();
    
dispatch_sync(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});
    
dispatch_sync(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});

// 列印輸出:
// <NSThread: 0x6000020198c0>{number = 1, name = main}
// <NSThread: 0x6000020191c0>{number = 1, name = main}

創建一個異步并發佇列

// 并發佇列
dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});

// 列印輸出:
// <NSThread: 0x6000020198c0>{number = 4, name = (null)}
// <NSThread: 0x6000020191c0>{number = 5, name = (null)}

創建一個異步串行佇列

// 串行佇列
dispatch_queue_t queue = dispatch_queue_create("com.haha", DISPATCH_QUEUE_SERIAL);
    
dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});
    
dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});

// 列印輸出:
// <NSThread: 0x6000020198c0>{number = 5, name = (null)}
// <NSThread: 0x6000020191c0>{number = 5, name = (null)}

在主佇列中,不論是同步還是異步都不會開啟子執行緒

dispatch_queue_t queue = dispatch_get_main_queue();

dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});

// 列印輸出:
// <NSThread: 0x6000020198c0>{number = 1, name = main}

但是使用sync函式往當前串行佇列中添加任務,會卡住當前的串行佇列(產生死鎖)

綜上所述可以用一張圖來概述

dispatch_get_global_queuedispatch_queue_create的區別

我們在代碼里分別創建兩種佇列,然后列印發現,全域佇列的地址都是同一個,而dispatch_queue_create的物件都不相同

dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue3 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue4 = dispatch_queue_create("queu4", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue5 = dispatch_queue_create("queu5", DISPATCH_QUEUE_CONCURRENT);

NSLog(@"%p %p %p %p %p", queue1, queue2, queue3, queue4, queue5);

// 分別輸出:0x10c5d8080 0x10c5d8080 0x6000037c3180 0x6000037c1580 0x6000037c3200

GCD的佇列組

第一種創建方式

// 1.創建佇列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
// 2.創建佇列組
dispatch_group_t group = dispatch_group_create();
   
// 3.把任務添加到佇列中 
dispatch_group_async(group, queue, ^{
  	NSLog(@"1----%@",[NSThread currentThread]);
});
    
    
dispatch_group_async(group, queue, ^{
   	NSLog(@"2----%@",[NSThread currentThread]);
});

// 4.攔截通知,當佇列組中所有的任務都執行完畢的時候回進入到下面的方法
dispatch_group_notify(group, queue, ^{     
    NSLog(@"-------dispatch_group_notify-------");
});

第二種創建方式

// 1.創建佇列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
// 2.創建佇列組
dispatch_group_t group = dispatch_group_create();
    
// 3.在該方法后面的異步任務會被納入到佇列組的監聽范圍,進入群組
// dispatch_group_enter|dispatch_group_leave 必須要配對使用

dispatch_group_enter(group);
    
dispatch_async(queue, ^{
   NSLog(@"1----%@",[NSThread currentThread]);
        
   //離開群組       	 
   dispatch_group_leave(group);
});

    
dispatch_group_enter(group);
    
dispatch_async(queue, ^{
   NSLog(@"2----%@",[NSThread currentThread]);
        
   //離開群組       	 
   dispatch_group_leave(group);
});
    
    
// 攔截通知
// 內部本身是異步的
dispatch_group_notify(group, queue, ^{
	NSLog(@"-------dispatch_group_notify-------");
});

第三種方式

// 1.創建佇列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
// 2.創建佇列組
dispatch_group_t group = dispatch_group_create();
   
// 3.把任務添加到佇列中 
dispatch_group_async(group, queue, ^{
  	NSLog(@"1----%@",[NSThread currentThread]);
});
    
    
dispatch_group_async(group, queue, ^{
   	NSLog(@"2----%@",[NSThread currentThread]);
});

// 4.會阻塞執行緒
// 直到佇列組中所有的任務都執行完畢之后才能執行
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"----end----");

其他常用方法

// 1.延遲執行的幾種方法
// 1.1
[self performSelector:@selector(task) withObject:nil afterDelay:2.0];

// 1.2
// repeats:是否重復呼叫
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

// 1.3
// 可以設定佇列控制在哪個執行緒執行延遲
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
   NSLog(@"GCD----%@",[NSThread currentThread]);
});


// 2.一次性代碼
// 整個程式運行程序中只會執行一次
// onceToken用來記錄該部分的代碼是否被執行過

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
  NSLog(@"---once----");
});


// 3.快速遍歷
// 開多個執行緒進行遍歷
/*
   第一個引數:遍歷的次數
   第二個引數:佇列(并發佇列)
   第三個引數:index 索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
   NSLog(@"%zd---%@",index,[NSThread currentThread]);
});

// 4.柵欄函式
// 柵欄函式不能使用全域并發佇列
// 柵欄函式之后的執行緒都會延后執行
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
});    
    
dispatch_barrier_async(queue, ^{
   NSLog(@"+++++++++++++++++++++++++++++");
});
    
dispatch_async(queue, ^{
   NSLog(@"%@", [NSThread currentThread]);
}); 

分析底層實作

原始碼下載

我們可以通過GCD的原始碼libdispatch.dylib來分析內部實作

libdispatch.dylib的下載地址:https://opensource.apple.com/release/macos-1015.html

然后找到libdispatch-1173.0.3進行下載

原始碼分析

dispatch_queue_create的底層實作

我們在queue.c檔案中搜索dispatch_queue_create,可以找到對應實作

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
	return _dispatch_lane_create_with_target(label, attr,
			DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

進入_dispatch_lane_create_with_target

DISPATCH_NOINLINE
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
		dispatch_queue_t tq, bool legacy)
{
	// dqai 創建
	dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

	//
	// Step 1: Normalize arguments (qos, overcommit, tq)
	// 第一步:規范化引數,例如qos, overcommit, tq

	dispatch_qos_t qos = dqai.dqai_qos;
#if !HAVE_PTHREAD_WORKQUEUE_QOS
	if (qos == DISPATCH_QOS_USER_INTERACTIVE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;
	}
	if (qos == DISPATCH_QOS_MAINTENANCE) {
		dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;
	}
#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

	_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;
	if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {
		if (tq->do_targetq) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
					"a non-global target queue");
		}
	}

	if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {
		// Handle discrepancies between attr and target queue, attributes win
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {
				overcommit = _dispatch_queue_attr_overcommit_enabled;
			} else {
				overcommit = _dispatch_queue_attr_overcommit_disabled;
			}
		}
		if (qos == DISPATCH_QOS_UNSPECIFIED) {
			qos = _dispatch_priority_qos(tq->dq_priority);
		}
		tq = NULL;
	} else if (tq && !tq->do_targetq) {
		// target is a pthread or runloop root queue, setting QoS or overcommit
		// is disallowed
		if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {
			DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "
					"and use this kind of target queue");
		}
	} else {
		if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {
			// Serial queues default to overcommit!
			overcommit = dqai.dqai_concurrent ?
					_dispatch_queue_attr_overcommit_disabled :
					_dispatch_queue_attr_overcommit_enabled;
		}
	}
	if (!tq) {
		tq = _dispatch_get_root_queue(
				qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
				overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
		if (unlikely(!tq)) {
			DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
		}
	}

	//
	// Step 2: Initialize the queue
	// 第二步:初始化佇列

	if (legacy) {
		// if any of these attributes is specified, use non legacy classes
		if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {
			legacy = false;
		}
	}

	// 拼接佇列名稱
	const void *vtable;
	dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
	if (dqai.dqai_concurrent) {
		vtable = DISPATCH_VTABLE(queue_concurrent);
	} else {
		vtable = DISPATCH_VTABLE(queue_serial);
	}
	switch (dqai.dqai_autorelease_frequency) {
	case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:
		dqf |= DQF_AUTORELEASE_NEVER;
		break;
	case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:
		dqf |= DQF_AUTORELEASE_ALWAYS;
		break;
	}
	if (label) {
		const char *tmp = _dispatch_strdup_if_mutable(label);
		if (tmp != label) {
			dqf |= DQF_LABEL_NEEDS_FREE;
			label = tmp;
		}
	}

	// 創建佇列,并初始化
	dispatch_lane_t dq = _dispatch_object_alloc(vtable,
			sizeof(struct dispatch_lane_s));
	// 根據dqai.dqai_concurrent的值,判斷是串行還是并發佇列
	_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
			DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
			(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
	
	// 佇列label的識別符號
	dq->dq_label = label;
	dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
			dqai.dqai_relpri);
	if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
		dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
	}
	if (!dqai.dqai_inactive) {
		_dispatch_queue_priority_inherit_from_target(dq, tq);
		_dispatch_lane_inherit_wlh_from_target(dq, tq);
	}
	_dispatch_retain(tq);
	dq->do_targetq = tq;
	_dispatch_object_debug(dq, "%s", __func__);
	return _dispatch_trace_queue_create(dq)._dq;
}

_dispatch_trace_queue_create內部會一步步呼叫_dispatch_introspection_queue_create -> _dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info,最終可以找到是通過_dispatch_introspection_lane_get_info通過模板來創建的佇列

DISPATCH_ALWAYS_INLINE
static inline dispatch_introspection_queue_s
_dispatch_introspection_lane_get_info(dispatch_lane_class_t dqu)
{
	dispatch_lane_t dq = dqu._dl;
	bool global = _dispatch_object_is_global(dq);
	uint64_t dq_state = os_atomic_load2o(dq, dq_state, relaxed);

	dispatch_introspection_queue_s diq = {
		.queue = dq->_as_dq,
		.target_queue = dq->do_targetq,
		.label = dq->dq_label,
		.serialnum = dq->dq_serialnum,
		.width = dq->dq_width,
		.suspend_count = _dq_state_suspend_cnt(dq_state) + dq->dq_side_suspend_cnt,
		.enqueued = _dq_state_is_enqueued(dq_state) && !global,
		.barrier = _dq_state_is_in_barrier(dq_state) && !global,
		.draining = (dq->dq_items_head == (void*)~0ul) ||
				(!dq->dq_items_head && dq->dq_items_tail),
		.global = global,
		.main = dx_type(dq) == DISPATCH_QUEUE_MAIN_TYPE,
	};
	return diq;
}

_dispatch_lane_create_with_target詳細步驟決議

1.呼叫_dispatch_queue_attr_to_info傳入dqa,創建dispatch_queue_attr_info_t型別的dqai,用于存盤佇列的相關屬性資訊

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
	dispatch_queue_attr_info_t dqai = { };

	if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
	// 默認為serial和null
	if (dqa == &_dispatch_queue_attr_concurrent) {
		dqai.dqai_concurrent = true;
		return dqai;
	}
#endif

	if (dqa < _dispatch_queue_attrs ||
			dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
		DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
	}

	size_t idx = (size_t)(dqa - _dispatch_queue_attrs);

	dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;

	dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;

	dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
	idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;

	dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;

	dqai.dqai_autorelease_frequency =
			idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;

	dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
	idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;

	return dqai;
}

2.通過_dispatch_object_alloc創建佇列dq

void *
_dispatch_object_alloc(const void *vtable, size_t size)
{
#if OS_OBJECT_HAVE_OBJC1
	const struct dispatch_object_vtable_s *_vtable = vtable;
	dispatch_object_t dou;
	dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);
	dou._do->do_vtable = vtable;
	return dou._do;
#else
	return _os_object_alloc_realized(vtable, size);
#endif
}

內部呼叫_os_object_alloc_realized,可以看出佇列內部也有一個isa指標,所以佇列也是物件

inline _os_object_t
_os_object_alloc_realized(const void *cls, size_t size)
{
	_os_object_t obj;
	dispatch_assert(size >= sizeof(struct _os_object_s));
	while (unlikely(!(obj = calloc(1u, size)))) {
		_dispatch_temporary_resource_shortage();
	}
	obj->os_obj_isa = cls;
	return obj;
}

3.通過_dispatch_queue_init設定佇列的相關屬性

static inline dispatch_queue_class_t
_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,
		uint16_t width, uint64_t initial_state_bits)
{
	uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);
	dispatch_queue_t dq = dqu._dq;

	dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |
			DISPATCH_QUEUE_INACTIVE)) == 0);

	if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {
		dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume
		if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {
			dq->do_ref_cnt++; // released when DSF_DELETED is set
		}
	}

	dq_state |= initial_state_bits;
	dq->do_next = DISPATCH_OBJECT_LISTLESS;
	dqf |= DQF_WIDTH(width);
	os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);
	dq->dq_state = dq_state;
	dq->dq_serialnum =
			os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);
	return dqu;
}

總結:

上述分析可以通過下圖來概述

dispatch_async的底層實作

queue.c中找到dispatch_async,其內部的實作如下

void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
	dispatch_continuation_t dc = _dispatch_continuation_alloc();
	uintptr_t dc_flags = DC_FLAG_CONSUME;
	dispatch_qos_t qos;

	// 任務包裝函式
	qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
	// 并發處理函式
	_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

1.在_dispatch_continuation_init中進行任務的封裝

DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
		dispatch_queue_class_t dqu, dispatch_block_t work,
		dispatch_block_flags_t flags, uintptr_t dc_flags)
{
	// 拷貝任務
	void *ctxt = _dispatch_Block_copy(work);

	dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		dc->dc_flags = dc_flags;
		dc->dc_ctxt = ctxt; // 賦值
		// will initialize all fields but requires dc_flags & dc_ctxt to be set
		return _dispatch_continuation_init_slow(dc, dqu, flags);
	}

	// 封裝任務,異步回呼
	dispatch_function_t func = _dispatch_Block_invoke(work);
	if (dc_flags & DC_FLAG_CONSUME) {
		func = _dispatch_call_block_and_release;
	}
	return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}

_dispatch_Block_invoke是一個宏,主要就是對任務的封裝

#define _dispatch_Block_invoke(bb) \
		((dispatch_function_t)((struct Block_layout *)bb)->invoke)

2.在_dispatch_continuation_async中并發處理函式

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
		dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
	if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
		_dispatch_trace_item_push(dqu, dc); // 跟蹤日志
	}
#else
	(void)dc_flags;
#endif
	return dx_push(dqu._dq, dc, qos);
}

其中的dx_push是一個宏

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

其中的dq_push需要根據佇列的型別,執行不同的函式

總結:

上述分析可以通過下圖來概述

dispatch_sync的底層實作

queue.c中找到dispatch_async,其內部的實作如下

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
	uintptr_t dc_flags = DC_FLAG_BLOCK;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
	}
	_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

然后呼叫到_dispatch_sync_f

static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
		uintptr_t dc_flags)
{
	_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}

然后呼叫到_dispatch_sync_f_inline,發現其內部是用柵欄函式實作的

static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
		dispatch_function_t func, uintptr_t dc_flags)
{
	if (likely(dq->dq_width == 1)) { // 表示串行佇列
		return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags); // 柵欄函式
	}

	if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
		DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
	}

	dispatch_lane_t dl = upcast(dq)._dl;
	// Global concurrent queues and queues bound to non-dispatch threads
	// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
	if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
		return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags); // 死鎖
	}

	if (unlikely(dq->do_targetq->do_targetq)) {
		return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
	}
	_dispatch_introspection_sync_begin(dl); // 處理當前資訊
	_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
			_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags))); // block執行并釋放
}

_dispatch_sync_f_slow中,當前的主佇列會被阻塞掛起

static void
_dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt,
		dispatch_function_t func, uintptr_t top_dc_flags,
		dispatch_queue_class_t dqu, uintptr_t dc_flags)
{
	dispatch_queue_t top_dq = top_dqu._dq;
	dispatch_queue_t dq = dqu._dq;
	if (unlikely(!dq->do_targetq)) {
		return _dispatch_sync_function_invoke(dq, ctxt, func);
	}

	pthread_priority_t pp = _dispatch_get_priority();
	struct dispatch_sync_context_s dsc = {
		.dc_flags    = DC_FLAG_SYNC_WAITER | dc_flags,
		.dc_func     = _dispatch_async_and_wait_invoke,
		.dc_ctxt     = &dsc,
		.dc_other    = top_dq,
		.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
		.dc_voucher  = _voucher_get(),
		.dsc_func    = func,
		.dsc_ctxt    = ctxt,
		.dsc_waiter  = _dispatch_tid_self(),
	};

	_dispatch_trace_item_push(top_dq, &dsc);
	__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);

	if (dsc.dsc_func == NULL) {
		dispatch_queue_t stop_dq = dsc.dc_other;
		return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
	}

	_dispatch_introspection_sync_begin(top_dq);
	_dispatch_trace_item_pop(top_dq, &dsc);
	_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
			DISPATCH_TRACE_ARG(&dsc));
}

總結:

上述分析可以通過下圖來概述

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/274023.html

標籤:iOS

上一篇:iOS底層原理(六)RunLoop

下一篇:iOS底層原理(七)多執行緒(中)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more