多线程之GCD(上)

多线程之GCD(上)

进程和线程

什么进程呢?进程是指在系统中正在运行的一个应用程序,也就是我们开发的每一个APP。每个进程之间是独立的,并且每个进程均运行在其专用的且受保护的内存空间内。通过“活动监视器”可以查看 Mac 系统中所开启的进程。
在这里插入图片描述
那什么事线程呢?线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。进程要想执行任务,必须得有线程,进程至少要有一条线程。程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。

进程和线程有什么关系呢?
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/Ocpu等,但是进程之间的资源是独立的。
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。

关于多线程
时间片:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片。
(单核CPU)同一时间,CPU 只能处理 1 个线程,换言之,同一时间只有 1 个线程在执行。
多线程同时执行是 CPU 快速的在多个线程之间的切换,CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果。如果线程数非常多,CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源,并且每个线程被调度的次数会降低,线程的执行效率降低。
我们通过一个API了解一下多线程:

// 在iphone13下打印
NSLog(@"%lu", (unsigned long)[NSProcessInfo processInfo].activeProcessorCount);

// 打印结果:8

这个打印的是设备能够支持线程的最大的并发数量。
当我们开启了10个线程的时候,并不是10个线程同时进行,而是通过CPU线程调度,来回切换执行,它并不是真正意义上的并发执行。
所以过多的线程是没有意义的。并且开启线程也是会占用内存的,主线程是1M,子线程是512kb,开启一条线程大概也需要90微秒的时间。

多线程的优缺点:
优点:
能适当提高程序的执行效率
能适当提高资源的利用率(CPU,内存)
线程上的任务执行完成后,线程会自动销毁
缺点:
开启线程需要占用一定的内存空间
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU 在调用线程上的开销就越大
程序设计更加复杂,比如线程间的通信、多线程的数据共享

关于线程的生命周期
在这里插入图片描述

线程池
在这里插入图片描述
GCD在内部维护着一个线程池。

NSLog(@"%@", [NSThread currentThread]);
for (int i = 0; i < 20; i++) {
	dispatch_async(dispatch_queue_create("LL", DISPATCH_QUEUE_SERIAL), ^{
		NSLog(@"%@", [NSThread currentThread]);
	});
}

在这里插入图片描述
也不会一直开启子线程,GCD会在线程池里面拿。
GCD线程池里最多缓存64条线程。

GCD

那什么是GCD呢?
全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数。GCD 是苹果公司为多核的并行运算提出的解决方案。GCD 会自动利用更多的CPU内核(比如双核、四核)。GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
了解GCD,首先我们从一下代码开始说起:

dispatch_queue_t queue = dispatch_queue_create("LL", DISPATCH_QUEUE_CONCURRENT); // 创建并行队列
NSLog(@"1");
dispatch_async(queue, ^{  // 异步
    NSLog(@"2");
    dispatch_sync(queue, ^{ // 同步
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

代码从上到下,1第一个打印。dispatch_async会把任务加入到并行队列,所以这个任务和5执行顺序就不一定了。dispatch_sync同步函数,会立即执行,所以234这样打印。
所以1先打印,234是有序的,5234顺序不一定。

dispatch_queue_t queue = dispatch_queue_create("LL", DISPATCH_QUEUE_SERIAL); // 创建串行队列
NSLog(@"1");
dispatch_async(queue, ^{  // 异步
    NSLog(@"2");
    dispatch_sync(queue, ^{ // 同步
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

这时候是串行队列。1是第一个打印的,dispatch_async会把任务加入到串行队列。由于队列是FIFO,所以整个代码属于一个任务,也就是1之后是5打印,然后执行dispatch_async加入的任务,2打印,dispatch_sync同步函数,任务加入队列,立即执行,但是dispatch_async加入的任务还没有执行完,所以造成了死锁。
可以把dispatch_sync改成dispatch_async,这样执行顺序就是15243

队列是FIFO,先进先出的。队列包括串行队列、并发队列、主队列(串行)、全局并发队列。队列只是用来存储任务的,没有能力去调度任务,线程才能调度队列里的任务。

源码解析GCD

首先我们从主队列开始插入:

// queue.h
/*!
 * @function dispatch_get_main_queue
 *
 * @abstract
 * Returns the default queue that is bound to the main thread.  // 返回绑定到主线程的默认队列。
 *
 * @discussion
 * In order to invoke blocks submitted to the main queue, the application must
 * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
 * thread. // 为了调用提交给主队列的块,应用程序必须调用dispatch_main(), NSApplicationMain(),或者在主线程上使用CFRunLoop。
 *
 * The main queue is meant to be used in application context to interact with
 * the main thread and the main runloop. // 主队列是用来在应用程序上下文中与主线程和主运行循环交互的。
 *
 * Because the main queue doesn't behave entirely like a regular serial queue,
 * it may have unwanted side-effects when used in processes that are not UI apps
 * (daemons). For such processes, the main queue should be avoided. // 因为主队列的行为并不完全像常规的串行队列,所以在非UI应用程序(守护进程)的进程中使用主队列可能会有不想要的副作用。对于这样的进程,应该避免使用主队列。
 *
 * @see dispatch_queue_main_t
 *
 * @result
 * Returns the main queue. This queue is created automatically on behalf of
 * the main thread before main() is called.  //返回主队列。这个队列是在main()被调用之前代表主线程自动创建的。
 */
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
dispatch_queue_main_t
dispatch_get_main_queue(void)
{
	return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
NSLog(@"%@", dispatch_get_main_queue());
// 打印:<OS_dispatch_queue_main: com.apple.main-thread>

主队列会设置名称,所以我们可以搜索com.apple.main-thread

// init.h
struct dispatch_queue_static_s _dispatch_main_q = {
	DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
	.do_targetq = _dispatch_get_default_queue(true),
#endif
	.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
			DISPATCH_QUEUE_ROLE_BASE_ANON,
	.dq_label = "com.apple.main-thread",
	.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1), // 串行队列DQF_WIDTH(1)
	.dq_serialnum = 1,  // 标识队列
};

_dispatch_main_q也就是dispatch_get_main_queueDISPATCH_GLOBAL_OBJECT传递的第二个参数。主队列也是串行队列,串行队列中的标识就是DQF_WIDTH(1)dq_serialnum标识是什么队列,1就是主队列。

// queue.h
/*!
 * @function dispatch_queue_create
 *
 * @abstract
 * Creates a new dispatch queue to which blocks may be submitted.  // 创建一个可以向其提交块的新分派队列。
 *
 * @discussion
 * Dispatch queues created with the DISPATCH_QUEUE_SERIAL or a NULL attribute
 * invoke blocks serially in FIFO order. // 使用DISPATCH_QUEUE_SERIAL或NULL属性创建的调度队列按FIFO顺序顺序调用块。
 *
 * Dispatch queues created with the DISPATCH_QUEUE_CONCURRENT attribute may
 * invoke blocks concurrently (similarly to the global concurrent queues, but
 * potentially with more overhead), and support barrier blocks submitted with
 * the dispatch barrier API, which e.g. enables the implementation of efficient
 * reader-writer schemes. // 使用DISPATCH_QUEUE_CONCURRENT属性创建的调度队列可以并发地调用block(类似于全局并发队列,但可能会带来更多的开销),并支持使用调度barrier API提交的barrier block,例如,它可以实现高效的读写模式。
 *
 * When a dispatch queue is no longer needed, it should be released with
 * dispatch_release(). Note that any pending blocks submitted asynchronously to
 * a queue will hold a reference to that queue. Therefore a queue will not be
 * deallocated until all pending blocks have finished. // 当不再需要调度队列时,应该使用dispatch_release()释放它。请注意,任何异步提交到队列的挂起块都将持有对该队列的引用。因此,在所有挂起的块完成之前,队列不会被释放。
 *
 * Passing the result of the dispatch_queue_attr_make_with_qos_class() function
 * to the attr parameter of this function allows a quality of service class and
 * relative priority to be specified for the newly created queue.  // 将dispatch_queue_attr_make_with_qos_class()函数的结果传递到该函数的attr参数,允许为新创建的队列指定服务类别的质量和相对优先级。
 * The quality of service class so specified takes precedence over the quality
 * of service class of the newly created dispatch queue's target queue (if any)
 * as long that does not result in a lower QOS class and relative priority. // 这样指定的服务分类质量优先于新创建的调度队列的目标队列(如果有的话)的服务分类质量,只要它不导致较低的QOS类和相对优先级。
 *
 * When no quality of service class is specified, the target queue of a newly
 * created dispatch queue is the default priority global concurrent queue. // 当没有指定服务分类质量时,新创建的调度队列的目标队列是缺省优先级全局并发队列。
 *
 * @param label
 * A string label to attach to the queue. // 附加到队列上的字符串标签。
 * This parameter is optional and may be NULL. // 该参数可选,可以为NULL。
 *
 * @param attr
 * A predefined attribute such as DISPATCH_QUEUE_SERIAL,
 * DISPATCH_QUEUE_CONCURRENT, or the result of a call to
 * a dispatch_queue_attr_make_with_* function. // 预定义的属性,如DISPATCH_QUEUE_SERIAL、DISPATCH_QUEUE_CONCURRENT或调用dispatch_queue_attr_make_with_*函数的结果。
 *
 * @result
 * The newly created dispatch queue. // 新创建的分派队列。
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
		dispatch_queue_attr_t _Nullable attr);

// queue.m
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_queue_create创建队列时,第二个参数标识着是串行队列还是并发队列,在注释中我们也可以看到,当我们传NULL时,默认是串行队列。

// queue.m
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)
{
		dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa); //dqa队列标识

	//
	// Step 1: Normalize arguments (qos, overcommit, tq)  // 规范化参数 我们不关注
	//
	
	......

	//
	// Step 2: Initialize the queue  // 初始化队列
	//
	
	......
	
	_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?  // 是并发队列 大于1 串行队列 1
		DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
		(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

	......
	
}

_dispatch_lane_create_with_target中我们关注第二个参数。

// queue.m
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; // 默认串行队列 dqai_concurrent == true

#if DISPATCH_VARIANT_STATIC
	if (dqa == &_dispatch_queue_attr_concurrent) {  // 并发队列 dqai_concurrent == true
		dqai.dqai_concurrent = true;
		return dqai;
	}
#endif

	......
	
}

_dispatch_queue_attr_to_info中,我们看到,默认情况下dqai_concurrentfalse_dispatch_queue_attr_concurrent并发时,dqai_concurrenttrue
所以在_dispatch_queue_init中我们看到判断dqf, dqai.dqai_concurrenttrue就传入DISPATCH_QUEUE_WIDTH_MAXfalse传入1

// queue_internal.h
/*
 * qf: queue full (bit 53)
 *    This bit is a subtle hack that allows to check for any queue width whether
 *    the full width of the queue is used or reserved (depending on the context)
 *    In other words that the queue has reached or overflown its capacity.
 */
#define DISPATCH_QUEUE_WIDTH_FULL_BIT		0x0020000000000000ull
#define DISPATCH_QUEUE_WIDTH_FULL			0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
#define DISPATCH_QUEUE_USES_REDIRECTION(width) \
		({ uint16_t _width = (width); \
		_width > 1 && _width < DISPATCH_QUEUE_WIDTH_POOL; })

DISPATCH_QUEUE_WIDTH_MAX是大于1的。

// inline_internal.h
// Note to later developers: ensure that any initialization changes are
// made for statically allocated queues (i.e. _dispatch_main_q). // 后面的开发人员请注意:确保所有初始化更改都是针对静态分配的队列(即_dispatch_main_q)。
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);  // DQF_WIDTH 串行是 1 并发 大于1
	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_queue_init初始化队列中DQF_WIDTH(1)就是串行队列,否则是并发队列。
其中:

// skip zero
// 1 - main_q  // 主队列
// 2 - mgr_q
// 3 - mgr_root_q
// 4,5,6,7,8,9,10,11,12,13,14,15 - global queues  // 全局队列
// 17 - workloop_fallback_q
// we use 'xadd' on Intel, so the initial value == next assigned
#define DISPATCH_QUEUE_SERIAL_NUMBER_INIT 17
extern unsigned long volatile _dispatch_queue_serial_numbers;

通过dq_serialnum我们也可以看到dq_serialnum = 1就是主队列。

DQF_WIDTH也就是类似于通道。DQF_WIDTH(1),串行队列就只有一个通道,DQF_WIDTH(大于1),并发队列是有多个通道。虽然队列是FIFO,但是并发队列由于是多通道,所以可以并发执行多个任务,串行队列就只能挨个来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值