进程和线程
什么进程呢?进程是指在系统中正在运行的一个应用程序,也就是我们开发的每一个APP
。每个进程之间是独立的,并且每个进程均运行在其专用的且受保护的内存空间内。通过“活动监视器”可以查看 Mac
系统中所开启的进程。
那什么事线程呢?线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。进程要想执行任务,必须得有线程,进程至少要有一条线程。程序启动会默认开启一条线程,这条线程被称为主线程或 UI
线程。
进程和线程有什么关系呢?
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU
调度,在每个时间片中只有一个线程执行)。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O
、cpu
等,但是进程之间的资源是独立的。
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
根本区别:进程是操作系统进行资源分配的基本单位,而线程是操作系统进行任务调度和执行的最小单位。
关于多线程
时间片: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
是有序的,5
和234
顺序不一定。
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_queue
中DISPATCH_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_concurrent
为false
,_dispatch_queue_attr_concurrent
并发时,dqai_concurrent
为true
。
所以在_dispatch_queue_init
中我们看到判断dqf, dqai.dqai_concurrent
,true
就传入DISPATCH_QUEUE_WIDTH_MAX
,false
传入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
,但是并发队列由于是多通道,所以可以并发执行多个任务,串行队列就只能挨个来了。