内容提要
文章指出iOS多线程的真正难点不在API调用,而在数据安全与效率。作者分析GCD的同步/异步任务、串行/并发队列及死锁成因,说明多线程操作共享数据需保证原子性:粒度过大退化为单线程,过小则锁失效。锁机制包括信号量、互斥量、读写锁等,需合理选择。最终难点在于原子性区域内的数据结构与算法效率,如YYCache用哈希表加双向链表实现O(1)操作。
延伸解读
原子性粒度:多线程效率的隐形调节器
文章指出,原子性区域的大小直接决定多线程是并行还是退化为串行。粒度过大,如用@synchronized包裹整个业务函数,会使多线程形同虚设;粒度过小,如仅依赖atomic属性,则无法保证复合操作的准确性。开发者需在数据安全与CPU利用率之间找到平衡点,这比单纯调用GCD API更考验对业务逻辑的理解。
锁的选择:从自旋到互斥的权衡
iOS提供多种锁,但本质基于信号量、互斥量、读写锁等少数机制。文章强调,OSSpinLock因优先级反转被弃用,而os_unfair_lock和pthread_mutex通过休眠等待降低CPU消耗。业务层常用NSLock和synchronized,组件库倾向os_unfair_lock和mutex。选择时需权衡性能、使用便利性及场景,而非盲目追求高性能锁。
数据结构与算法:锁内效率的终极战场
即使锁使用得当,原子性区域内的代码仍由单线程执行,其效率至关重要。文章以YYCache为例,其用pthread_mutex控制小粒度锁,内部通过哈希表加双向链表实现O(1)的增删查。这揭示多线程编程的深层难点:在锁保护的临界区内,高效的数据结构与算法设计才是提升整体并发性能的关键。
Q&A
iOS多线程开发中,为什么说真正的难点不是API调用而是数据安全?
因为GCD等API已经封装得很好,死锁等问题容易排查,而多线程操作共享数据时,若不能保证原子性,会导致数据间歇性不稳定,成为App的隐藏地雷,因此数据安全才是核心难点。
GCD中同步任务和异步任务的关键区别是什么?
同步任务必须在当前线程执行,不具备开启新线程的能力,且必须执行完才能继续后续代码;异步任务具备开启新线程的能力(且一定要开启),后续代码会立刻执行,任务执行一定不在当前线程。
在串行队列中嵌套同步任务为什么容易导致死锁?
因为串行队列一次只能执行一个任务,同步任务会阻塞当前线程并等待队列中前面的任务完成,若当前线程正是该队列的执行线程,则形成相互等待,导致死锁。
多线程中原子性粒度大小如何影响性能和数据准确性?
粒度过大(如锁住整个函数)会使多线程退化为单线程,降低并发性能;粒度过小(如仅用atomic修饰属性)则无法保证复合操作的原子性,导致数据不准确。需要权衡找到合适的粒度。
iOS中常用的锁有哪些?各自有什么特点?
iOS常用锁包括:OSSpinLock(自旋锁,已废弃)、os_unfair_lock(互斥锁,替代自旋锁)、dispatch_semaphore(信号量)、pthread_mutex(互斥锁,可派生递归锁和条件锁)、NSLock、NSRecursiveLock、NSCondition、@synchronized(递归锁,性能最低)、dispatch_barrier_async(栅栏锁)等。它们大多基于信号量、互斥量、读写锁、条件变量等机制封装。
为什么多线程编程最终考量的是数据结构和算法?
因为原子性区域内的代码是单线程执行的,若执行效率低会拖累整体性能。使用高效的数据结构和算法(如YYCache用哈希表+双向链表实现O(1)操作)能减少原子性区域内的耗时,从而提升多线程效率。