GCD基础知识
1.GCD定义
- GCD全称是Grand Central Dispatch
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 将任务添加到队列,并指定任务执行的函数
2.函数
- dispatch_sync 同步执行
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程,即不具备开启新线程的能力
- dispatch_async 异步执行
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)
3.队列
-
队列是一种特殊的线性表,遵循先进先出(FIFO)原则。
-
队列主要分为串行队列(Serial Dispatch Queue) 和 并发队列(Concurrent Dispatch Queue)两种
-
串行队列:每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行)
- 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);创建串行队列
- 其中的DISPATCH_QUEUE_SERIAL也可以使用NULL表示,这两种均表示 默认的串行队列
- dispatch_queue_t serialQueue1 = dispatch_queue_create("com.CJL.Queue", NULL); dispatch_queue_t serialQueue2 = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_SERIAL);
-
并发队列:一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行)
- 用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT)
- 注意:并发队列的并发功能只有在异步函数下才有效
-
主队列:GCD中提供的特殊的串行队
- 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
- 使用dispatch_get_main_queue()获得主队列
- 通常在返回主线程 更新UI时使用
-
全局并发队列(Global Dispatch Queue):GCD提供的默认的并发队列
- 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
- 第一个参数表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在ios9之后,已经被服务质量(quality-of-service)取代、第二个参数使用0
4.函数与队列组合
- 串行队列 + 同步函数
- 串行队列 + 异步函数
- 并发队列 + 同步函数
- 并发队列 + 异步函数
- 主队列 + 同步函数
- 主队列 + 异步函数
- 全局并发队列 + 同步函数
- 全局并发队列 + 异步函数
GCD常见使用函数
-
dispatch_after
-
dispatch_once
-
dispatch_apply
-
dispatch_group_t
-
dispatch_barrier_sync & dispatch_barrier_async
-
dispatch_semaphore_t
-
dispatch_source_t
死锁条件
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
atomic底层实现原理
- 系统生成的getter/setter方法会进行加锁操作
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
- spinlock已经由于存在优先级反转问题被弃用并用os_unfair_lock替代。既然被弃用了,这里为什么还在用;原因是进入spinlock去看会发现,底层已经被os_unfair_lick替换
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
public:
constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
lockdebug_remember_mutex(this);
}
constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
void lock() {
lockdebug_mutex_lock(this);
os_unfair_lock_lock_with_options_inline
.
.
.
关于atomic
不绝对安全。举例说明:线程1 调用getter 线程2 调用setter 线程3 调用setter 这3个线程并行同时开始,线程1会get到一个值,但是这个值不可控,可能是线程2,线程3 set之前的原始值,可能是线程2 set的值,也可能是线程3 set的值。
@synchronized底层实现
- 底层原理
开启汇编: objc_sync_enter、objc_sync_exit
1.objc_sync_enter 、objc_sync_exit底层分析
传入objc为nil 调用objc_sync_nil,什么都没做,直接return
传入objc不为空,通过id2data获取SyncData SyncData* data = id2data(obj, RELEASE)
objc_sync_enter对threadCount、lockCount进行递增操作objc_sync_exit递减操作
SyncData是一个结构体,主要用来表示一个线程data,类似于链表结构,有next指向,且封装了recursive_mutex_t属性,可以确认@synchronized确实是一个递归互斥锁
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;//类似链表结构
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;//递归锁
} SyncData;
SyncCache也是一个结构体,用于存储线程,其中list[0]表示当前线程的链表data,主要用于存储SyncData和lockCount
typedef struct {
SyncData *data;
unsigned int lockCount; // number of times THIS THREAD locked this block
} SyncCacheItem;
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
SyncCacheItem list[0];
} SyncCache;
2.@synchronized使用链表的原因是链表方便下一个data的插入
3.性能低的原因:由于底层中链表查询、缓存的查找以及递归,是非常耗内存以及性能的,导致性能低
多线程常见的锁
OSSpinLock
自旋锁
不安全,iOS 10 已启用
os_unfair_lock
互斥锁
替代 OSSpinLock
pthread_mutex
互斥锁
PTHREAD_MUTEX_NORMAL、#import <pthread.h>
pthread_mutex (recursive)
递归锁
PTHREAD_MUTEX_RECURSIVE、#import <pthread.h>
pthread_mutex (cond)
条件锁
pthread_cond_t、 #import <pthread.h>
pthread_rwlock
读写锁
读操作重入,写操作互斥
@synchronized
互斥锁
性能差,且无法锁住内存地址更改的对象
NSLock
互斥锁
封装 pthread_mutex
NSRecursiveLock
递归锁
封装 pthread_mutex (recursive)
NSCondition
条件锁
封装 pthread_mutex (cond)
NSConditionLock
条件锁
可以指定具体条件值
dispatch_once实现原理
1.只执行一次原因:GCD单例中,有两个重要参数,onceToken 和 block,其中onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return。
2.多线程影响:如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的
3.block调用时机:如果此时任务没有执行过,则会在底层通过C++函数的比较,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行,会直接返回
内存五大区域
1.栈区(Stack)
- 栈是向低地址扩展的数据结构
- 栈是一块连续的内存区域,遵循先进后出(FILO)原则
- 栈的地址空间在iOS中是以0X7开头
- 栈区是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数,例如函数的隐藏参数(id self,SEL _cmd
2.堆区(Heap)
3.全局区(静态区,即.bss & .data)
4.常量区(即.rodata)
5.代码区(即.text)
线程生命周期
新建 - 就绪 - 运行 - 阻塞 - 死亡