iOS之多线程零碎笔记

520 阅读6分钟

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,说明任务已经处理过一次了,直接return2.多线程影响:如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的
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)

线程生命周期

新建 - 就绪 - 运行 - 阻塞 - 死亡