进程和线程
进程
- 进程是指在系统中正在运行的一个应用程序;
- 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内;
- 在MAC系统下,通过‘活动监控’可以查看运行的进程;
线程
- 线程是进程的基本执行单元,一个进程的所有任务都是在线程中执行;
- 进程要执行任务,必须有线程,并且至少有一条线程;
- 程序启动的时候会默认开启一条线程,这条线程被称为主线程或者UI线程;
进程和线程的关系
- 同一进程中的线程共享本进程的地址空间,进程之间的地址空间是独立的;
- 同一进程中的线程共享本进程中的资源如内存、I/O、cup等,但是进程之间的资源是独立的;
多线程的意义
优点
- 能适当提高程序的执行效率;
- 能适当提高资源的利用率如(cpu、内存等);
- 线程上的任务完成后,线程会自动销毁;
缺点
- 开辟线程需要占用一定的内存空间(默认情况下,主线程占1M,每个线程都占512KB)
- 如果开辟大量线程,会占用大量的内存空间,降低程序的性能;
- 线程越多,cpu在调度线程中开销越大;
- 程序设计更复杂,比如线程之间的通信、数据共享等;
线程的生命周期
当一个线程创建后,并不会立即执行,会处于Runnable状态,当CUP调度当前线程,才会被运行,当cup调度其它线程的时候,当前线程又处于Runnable状态,当执行线程执行完成就会死亡,当调用Sleep等待同步锁从可调度线程池移除就会阻塞,当Sleep到时、获取同步锁添加回可调度线程池就有进入Runnable状态。
线程池
GCD中线程池中缓存了64条线程,一个进程同时有多少进程执行任务,是有线程池缓存的数量决定的。
GCD
CGD简介
GCD的全程是Grand Central Dispatch,是Apple基于C语言开发的一套多线程开发机制,提供了非常多的强大的函数。
GCD的优势
- GCD是苹果公司为多核的并行运算提出的解决方案;
- GCD 会自动利用更多的CPU内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码;
GCD队列
队列是先进先出(FIFO, First-In-First-Out)的线性表,只是用来存储任务的,没有能力来调度任务。CGD的队列有四种:串行队列、并发队列、全局并发队列、主队列。
- 串行队列(serial dispatch queue)一个任务执行完后执行下一个任务,从源码可以看到它的DQF_WIDTH等于1,相当以它只有⼀条通道。所以队列中的任务要串⾏执⾏,也就是⼀个⼀个的执⾏,必须等上⼀个任务执⾏完成之后才能开始下⼀个,⽽且⼀定是按照先进先出的顺序执⾏的,⽐如串⾏队列⾥⾯有4个任务,进⼊队列的顺序是a、b、c、d,那么⼀定是先执⾏a,并且等任务a完成之后,再执⾏b... 。
dispatch_queue_t q = dispatch_queue_create("yl", DISPATCH_QUEUE_SERIAL);
- 并发队列(concurrent dispatch queue)可以同时执行多个任务,从源码可以看到它的DQF_WIDTH⼤于1,相当于有多条通道。队列中的任务可以并发执⾏,也就任务可以同时执⾏,⽐如并发队列⾥⾯有4个任务,进⼊队列的顺序是a、b、c、d,那么⼀定是先执⾏a,再执⾏b...,也是按照先进先出(FIFO, First-In-First-Out)的原则调⽤的,但是执⾏b的时候a不⼀定执⾏完成,⽽且a和b具体哪个先执⾏完成是不确定的。通道有很多,哪个任务先执⾏完得看任务的复杂度,以及cpu的调度情况。
dispatch_queue_t q = dispatch_queue_create("yl", DISPATCH_QUEUE_CONCURRENT);
- 全局并发队列-获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程,执行过程和并发队列一致
dispatch_queue_t q1 = dispatch_get_global_queue(0, 0)
- 主队列- 不会开启线程,主线程串队列
dispatch_queue_t q = dispatch_get_main_queue()
GCD函数
同步函数
dispatch_queue_t q = dispatch_queue_create("yl", DISPATCH_QUEUE_SERIAL);
dispatch_sync(q, ^{
});
- 立即执行;
- 阻塞当前线程;
- 不具备开启子线程的能力
异步函数
dispatch_queue_t q = dispatch_queue_create("yl", DISPATCH_QUEUE_SERIAL);
dispatch_async(q, ^{
});
- 不会阻塞当前线程;
- 可以开辟子线程;
- 不会立即执行
GCD死锁
写如下代码,因为死锁崩溃后,打印堆栈信息,返回如下__DISPATCH_WAIT_FOR_QUEUE__信息:
uint64_t dq_state = _dispatch_wait_prepare(dq);
if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
"dispatch_sync called on queue"
"already owned by current thread");
}
我们在GCD源码中查找,最终找到如下函数
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
// equivalent to _dispatch_lock_owner(lock_value) == tid
return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
其中tid代码线程的id,lock_value代表当前队列,DLOCK_OWNER_MASK是一个较大的数字,当(lock_value ^ tid) 是零的时候&DLOCK_OWNER_MASK等于零,当lock_value 和tid相等时候(lock_value ^ tid) 是零;
- 死锁的原因:"dispatch_sync called on queue","already owned by current thread"
- 结论:在当前线程(和当前队列相关)同步向串行队列里面添加任务,就会死锁
例子1
例子2
例子3
例子4
单例
在开发中一般用dispatch_once来实现单例,如下:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"单例初始化等");
});
- 单例两个重要参数
dispatch_once_t和block,可以从源码中看到在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量uintptr_t v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return; - block调用的时机,当任务没有执行的时候,将任务状态进行加锁,变
DLOCK_ONCE_UNLOCK,然后执行block,当block执行完成后,进行解锁,将状态改为DLOCK_ONCE_DONE,这样保证当前任务的唯一性; - 当进入是状态是
DLOCK_ONCE_UNLOCK时候,当前任务正在执行,就会进入无限等待,直到任务状态变成DLOCK_ONCE_DONE;
栅栏函数
CGD栅栏函数有两种
- 同步栅栏函数
dispatch_barrier_sync:不开辟线程,在主线程中执行,前面的任务执行完后,会执行栅栏函数,会阻塞主线程,执行完后会执行后面函数; - 异步栅栏函数
dispatch_barrier_async:前面的任务执行完毕才会来到这里,开辟线程,不会阻塞主线程
- 栅栏函数只能控制同一并发队列;
- 栅栏函数可以看成
函数和栅栏,栅栏阻挡队列后面的任务执行,同步函数和异步函数相同; - 在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,没有任何意义;
调度组
dispatch_group_t g = dispatch_group_create();
dispatch_queue_t que1 = dispatch_queue_create("yl1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t que2 = dispatch_queue_create("yl2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(g, que1, ^{
sleep(3);
NSLog(@"------1");
});
dispatch_group_async(g, que2, ^{
sleep(2);
NSLog(@"----6");
});
dispatch_group_enter(g);
dispatch_async(que1, ^{
sleep(2);
NSLog(@"1");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(que2, ^{
sleep(3);
NSLog(@"2");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0 ), ^{
sleep(4);
NSLog(@"3");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(dispatch_get_main_queue(), ^{
sleep(5);
NSLog(@"4");
dispatch_group_leave(g);
});
dispatch_group_notify(g, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
调度组,有如上两种写法dispatch_group_async和dispatch_group_enter、dispatch_group_leave,dispatch_group_enter和dispatch_group_leave是成对出现的,调度组前面的任务执行完成后调用dispatch_group_notify后的函数。调度组和队列没有关系,只要是同一调度组就可以。
信号量
信号量主要的三个函数
dispatch_semaphore_create(long value)这个函数是创建⼀个dispatch_semaphore_t类型的信号量,并且创建的时候需要指定信号量的⼤⼩。dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)等待信号量。如果信号量值为0,那么该函数就会⼀直等待,也就是不返回(相当于阻塞当前线程),直到该函数等待的信号量的值⼤于等于1,该函数会对信号量的值进⾏减1操作,然后返回。dispatch_semaphore_signal(dispatch_semaphore_t deem)发送信号量。该函数会对信号量的值进⾏加1操作。
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"4");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
dispatch_semaphore_signal(sem);
});
上面1,2,3,4,5顺序执行。
- 通过信号量可以控制GCD最大并发量;
- dispatch_semaphore_wait 和 dispatch_semaphore_signal ⼀定要成对出现。因为在信号量释放的时候,如果dsema_orig初始信号量的⼤⼩(dispatch_semaphore_create(long value)中value)⼤于dsema_value(通过dispatch_semaphore_wait和dispatch_semaphore_signal改变之后的信号量的⼤⼩)就会触发崩溃。
void
_dispatch_semaphore_dispose(dispatch_object_t dou,
DISPATCH_UNUSED bool *allow_free)
{
dispatch_semaphore_t dsema = dou._dsema;
//dsema_value创建信号量的大小(dispatch_semaphore_create(long value)中value)
//dsema_orig ++--后的信号量大小
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(dsema->dsema_orig - dsema->dsema_value,
"Semaphore object deallocated while in use");
}
_dispatch_sema4_dispose(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}
dispatch_source
dispatch_source是⽤来监听事件的,可以创建不同类型的dispatch_source来监听不同的事件。
dispatch_source可监听的事件类型:
dispatch_source的⼏个⽅法:
- dispatch_source_create 创建源
- dispatch_source_set_event_handler 设置源事件回调
- dispatch_source_merge_data 源事件设置数据
- dispatch_source_get_data 获取源事件数据
- dispatch_resume 继续
- dispatch_suspend 挂起
- dispatch_source_cancel 取消源事件 定时器:
__block int timeout = 60;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
if(timeout <= 0){
dispatch_source_cancel(_timer);
}
else{
timeout--;
NSLog(@"倒计时:%d", timeout);
}
});
dispatch_resume(_timer);