欢迎阅览iOS探究系列(按序阅览食用效果愈加)

  • iOS探究 alloc流程
  • iOS探究 内存对齐&malloc源码
  • iOS探究 isa初始化&指向剖析
  • iOS探究 类的结构剖析
  • iOS探究 cache_t剖析
  • iOS探究 办法的实质和办法查找流程
  • iOS探究 动态办法解析和消息转发机制
  • iOS探究 浅尝辄止dyld加载流程
  • iOS探究 类的加载进程
  • iOS探– g n j 2 W ?究 分类、类拓展的加载进程
  • iOS探究 isa面试题剖析
  • iOS探究 runtime面试题剖析
  • iOS探究 KVC原理及自m : K ] U A界说
  • iOS探究 KVO原理及自界说
  • iOS探究 多线程原理
  • iOS探究 多线程之GCD运用
  • iOS探究 多线程之GCD底层剖析
  • iOS探究 多线程之NSOperation
  • iO6 C bS探究 多线程面试题剖析
  • iOS探究 细数iOS中的那些锁

写在前面

多线程在日常开发中能起到– / % w g功能优化的效果,? T b B ; 7 m可是一旦没用好就会形成线程不安全,本文就来讲讲怎么确保线程安全

一、锁

1.线程安全

当一个线程拜访数据的时分,其他的线程不能对其进行拜访| 0 ? : V ly % 8 k C 6 2直到该线程拜访完毕。简略来讲便y I _ g 1 3 Z .是在同一时刻,对同一个数据操作的线程只要一个。而线程不安全,则是在同一时刻能够有多个线程对该数据进行拜访,然后得不到预期的成果

即线程内操作了一个线程外的非线程安全变量,这个时分一定要考虑线程安全和同步

2.检测安全

iOS探索 细数iOS中的那些锁

3.锁j R F的效果

作为一种非强制的机制,被用来确保线程安全。每一个线程在拜访数据或许资源前,要先获取(Acquire)锁,并在拜访完毕之后开释(Release)锁。假如锁现已被占用,其它企图获取锁的线程会等候,直到锁从头可用

注:不要将过多的其他操作代码放到锁里边,不然一个线程履行的时分另N a ! 5 Q一个线程就一向在等候,就无法发挥多线程的效果了

4.锁的分类

在iOS中锁的基本品种只要两种:互斥锁{ b F自旋~ 3 ) ! T,其他的比方条件锁递归锁信号量都是上层的封装和完成

而在JAVA中锁占有更大份额,有兴趣能够去研究一下

5. 互K 2 ? )斥锁

互斥锁(Mutual exclusion,缩写Mutex)避免两条线程一起对同一公共资源(比方大局变量)进行读写的机制。当获取锁操作失败时,线y K 1 K R – n Z程会进入睡觉,等候锁开释时被唤醒

互斥锁又分为:

  • 递归锁:可重! ? Z入锁,同一个线程在锁开释前可再次获取锁,即能够N + p p Y @ M递归调用
  • 非递归锁:不可重入,有必要等锁开释后才干再次获取锁

6. 自旋锁

自旋锁:线程反复检查锁变量是否可⽤。由于线程在这⼀进程中坚持执⾏,
因而是⼀种忙等候。⼀旦获取了⾃旋锁,线程会⼀直坚持该锁,直⾄显式释
放⾃旋锁

⾃旋锁% V _ N E 9 *避免了进程上下⽂的调度开支,因而关于线程只会堵塞很短时刻的场合是有效的

7.互斥锁和自旋锁的差异

  • 互斥锁在线程获取Q U r J N E锁但没有获取到时,线程会进入休眠状态,等锁被开释时线程会被唤醒
  • 自旋锁的线程则会一向处于等候状态(忙等候)不会进入休眠—— 2 j O X 8 @ 4因而效率高

接下] y 来就一一来介绍iOS顶用到的各种锁

二、自旋锁

1.OSSpinLock

自从OSSpinLock呈现了安全问题之后就废弃了。自旋锁之所以不安全,是由于自旋锁由于获取锁时,线程N v V L J 1会一向处于忙等候状态,形成了使命的优先级回转

OSSpinLock忙等的机制就或许形成高优先级一向runnE K q E *ing等候{ d _ _ N 3 _ B,占用CPU时刻片;而低优先级使命无法抢占时刻片,变成迟@ ~ z ] S / I b L迟完不成,不开释锁的状况

2.atomic

2.1 atomic原理

在iOS探究 KVC原理及自界说中有说到自动生成的s{ u y oetter办法会依据润饰符不同调用不@ * + {同办法,最后统一调用reallySetProperty办法,其间就F M p } k % 5 w有一段关于atomic润饰词的代码

static inline void reallySetProperty(id self, SEL _cmd, id newVa1 . T S n W H Flue, ptrdiff_t offset, bool atomic, bool copy, bool mu: B U a H d @ % `tableCopy)
{
if (offset == 0) {
object_setClam 1 ( Y . Z a ess(self, newVaF $ # F S J z 5 %lue);
return;
}
id oldValue;
id *slot` 5 X h 2 = (id*) ((char*)self + offset);
if (copy) {
newValue = [newVal! @ } 6 P Kue copyWX a x ) k U A MithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue)| S e # R C Z i s return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;u O ]
*slot = newValue;
} else {
spinlockH i ._t^ g a& slotlu b ; Z T .ock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*sM 2 v I 7 Flot = newValue;
slotlock2 d # p a 4 L H.unlock();
}
objc_release(oldValue);
}

比对一下atomic的逻辑分支:

  • 原子性润饰的特点进行了spinlock加锁处理
  • 非原子性的特点除了没加锁,其他逻辑与atomic一般无二

等等,前面不是刚说OSSpinLock由于安全问题被废弃R v r D ! $ % s了吗,可是苹果源码怎么还在运用呢?其实点进去就会发现用os_unfair_lock代替了OSSpinLock(iOS10之后替换)

using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy- r * Z R G_t {
osi $ & +_unfair_lock mLocr ) nk;
...
}

一起为了哈希不抵触,还运用加盐操作进行加锁

gette, B 1r办法亦是如此:atomic润饰的n q I G特点进行加锁处理

id objc_get| @ r } e 7 o  %Property(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offsetQ E J V h t == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinO { o j i Hlock_t& slotlock = PropertyLocc g 2ks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotl8 ~ : i ! + U D nock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return o| ( ibjc_autorel6 t  V % K Q 2easeReturnValue(value);
}
2.2 atoZ U i 6mic润饰的特点绝对安全吗?

atomic只能确保setter、getter办法的线程安全,并不能确保数据安全

iOS探索 细数iOS中的那些锁

如上图所示,被atomic润饰的index变量别离在两次并发异步for循环10000次后输出的成果并不y g @等于20000。由此能够得出结论:

  • atomic确保变量在取值和赋值时的线程安全
  • 但不能确保self.index+1也是安全的
  • 假如改成self.indj j 7ex=i是能确保setter办r J ~ 5 w W法的线程安全的

3. 读写锁

读写锁实践是一种特殊的自旋锁,它把对共享资源的拜访者划分红读者和写者,读者只对共享资源进行读拜访,写者则需求对共享资源进行写操作。这种锁相关于自旋锁而言,能提高并发性,由于在多处理器体系中,它允许一起0 / R有多个读者来拜访共享资源,最大或许的读者数为实践的CPU

  • 写者是排他性的,⼀个读写锁一起只能有⼀个写者或多个读者(与CPU数相关), T S K @ 1 v A但不能一起既有读者⼜有写者。在读写锁坚持期间也是抢占失效的
  • 假如读写锁当时没有读者,也没有写者,那么写者能够⽴刻获得读写w ` ` p T 0锁,不然它有必要⾃旋在那⾥,直到没有任何写者或读者。假如读写锁没有写者,那么读者能够⽴即获得该读写锁,不然读者有必要⾃旋在那⾥,直到写者开释该读写锁
/V w $ n c w ^ ]/ 导入头文件
#import <pthread.j y 0 M c g y }h>
// 大局声明读写锁
pthread_rwlock_t lock;
// 初始化读写锁
pthread_rwlock_init(&lock, NULL);
// 读操作-加锁
pthread_rwlock_rdlock(&lock);
// 读操作-测验加锁
pthread_rwlock_tryrdlock(&lock);
// 写操作-加锁
pthread_rwlock_wrlock(&lockO p ` F l G);
// 写操作-测验加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock)h v L q };
// 开释锁
pthread_rwlock_destroy(&lock);

平常很少会直接运用读写锁pth. a 2 %read_rwlY ^ Y f p 9 Q Dock_t,更多r $ H p ) r h的是选用其他办法,例如运用栅门函u ; y | W数完成读写锁的需求

三、互斥锁

1.pthread_mutex

pthread_mutex便是互斥锁自身——当锁被占用,而其他线程请求锁时,不是运用忙等,而是堵塞线程并睡觉

运用如下:

// 导入头文件
#import <pthread.h>5 d K x : Q 1;
// 大局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&E G G Q u * * B Mamp;_lock, NULL);
/q G w W d : , F/ 加锁
pthread_mutex_lock(&_lock);
// 这儿做需求线程安全操作
// ...
// 解锁 
pthread_mutex_unlock(&_lock);
// 开释锁
pthread_mutex_destroy(&_lock);

YYKit的YYMemory8 X # s ,Cach有运用到pthread_mutex

2.@synchronized

@synchronized% + | i许是日常开发顶用的比较多的一种互斥锁,由于它的运用比较m . u F ( r简略,但并不是在恣意场景下都能运用@synchronized,且它的功能较低

@synchronized (obj) {}

接下来就经过源码探究来看一下@synchronized在运用中的留意事项

  • 经过汇编能发现@synchronized便是完成了objc_sync_enterobjc_sync_exit两个办法
  • 经过n = _ 7 z R n b号断点能知道这两个办法都是在objc源码中的
  • 经过clang也能S o G ]得到一些信息:
int main(i} 9 & e H ? An7 S B 4 [ Y l j )t argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleaseP8 b j e D `ool __au! } .tore6 a Vleasepool;
appDelegateClassName = NSStringFromClass(((ClA | $ V F )ass (*)(id, SEL))(void *)objc_msgSU & z 1end)((id)objc_M $ % N y X qgetClass("AppDelegate"), sel_registerName("class")));
{
id _rethrow = 0;
id, n M * D  | _sync_ob# L Jj = (id)appDelegateClassName;
objc_sync_enter(_sC 8 ] f ^ G % Eync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT, r . G .(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {
obj@ k t | P zc_sync_exit(sy/ v P + Y J lnc_6 $ p R :exit);
}
id sync_exit;
}
_sy6 ; % ,nc_exit(_sync_obj);
}
catch (id e) {G B + F K ,_reL M D n ; o . z Fthrow = e;}
{
struct _FIN { _FIN(id reth) : rethrS 7 Cow(reth) {}
~_FIN() { iL ; y a If (rethrow) objc_exception_throw(+ Z Y _ R ` c 9rethrow); }
id rethry E S * k x row;
}_fin_force_rethow(_rethrow);
}
}
}
return UIApplica2 # Y s S 6tionMain6 M } ^ & g x H ,(argc, argv, __null, appDelegateClassk 7 FN^ w #  } v C ] Lame);
}
2.1 源码剖析

objc源码中找到objc_synck 6 : N z R ) 6_enteror n ~ = P p G cbjc_sync_exit

// Begin synchronizing on 'obj'V D 1. 
// Allocates recursiveP @ B ~ N mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.  
int objc_sync_enter(id obj)
{
int r^ g U T r V desult = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nw J } . Q e b UothQ g I A / X $ing
if (DebugNilSync) {
_objc_i= G ? [ B Rnform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_n6 ) q % # e  2il();
}
return result;
}
// End synchroniz` _ A ring on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NP J a Y NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYN# 4 t /C_SUCCESS;6 A * 6
if (obj) {
Synm r x O g } 2 i WcData* data = id2data(obj, REa j t 7LEASE);
if (!data) {
res1 ! ]ult = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mu) b 5 m = n ; y jtex.tryUnlock();
if (!okay)- # $ G c P r {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return resultA q 4;
}
  1. 首先从它的注释中rec; i _ e c o qursive mutex能够得出@synchronizedk B e % 3 q J A D递归锁
  2. 假如锁的目标obj不存在时别离会走objc_sync_nil()不做任何操作(源码剖析能够先处理简略的逻辑分支)
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);

这也是@synchronize. i u Q 8 b Id作为递归锁但能避免l 0 6 ] H k c死锁的原因所在:在不断递归的进程中假如目标不存在了就会中止递归然后避免死锁

  1. 正常状况下(obj存在)会经过id2data办法生成一个SyncData目标
  • nextData指的是链表中下一个SyncData
  • object指的是@ z N当时加锁的目标
  • threadCount表明& W e q b 2运用该目标进行加锁的线程数
  • mutex即目标所相关的锁
typedef s[ 6 7 / | h ^truct alignas(CacheLineSize) SyncData {
struct SyncDat- ) B $ & c y Ua* nextData;
DisguisedPtr<objc_object> object;
int32_t threa4 A y { X &dCount;  // number of THREADS usinT 2 Y pg this block
recursive_mutex_t mutex;
} SyncData;
2.2 准备SyncDatar ( Z
sJ =  o ltaS x # | m ] _tic SyncDatH o } 3 # s Y fa* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ& 3  T U [ y k(object);
SyncData **listp = &LIST_FOR_OBJ(objO r E xect);
SyncData* resultP b X f & = NULL;
...
}

id2data先将j 1 _ b y Q J d回来目标SyncData类型的result准备好,后续进行数据填充

#define LOCK_FOR_OBJ(obj) sDataLists3 E } M j V ] j 5[obj].lock
#define LIST_FOR{ K u ( V 0_OBJ(obj) sD~ _  A 6 * D )ataLists[obj].data
static StT ! U 0 w 3 sripedMap<SP N 7 $ 6 x Z 2 ayn | n A w  N V &ncList _ E { s D 3 h Q> sDataLists;
sm P ] B U | | Vtruct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncLi] M V r l Vst() : data(nil),, L W 3 A 2 | lock(fork_unsafe_lock) { }
};

其间经过两个宏界说去获得SyncList中的datalock——static StripedMap<SyncList> sDataLists 能够了解成 NSArray<id> list

既然@synchronized能在恣意当地(VC、View、Model等)运用,那么底层必定保护着一张大局的表(相似于weak表)。而从SyncListSyncData的结构能够证实体系确真实底层保护着一张G ? . 0 , a N i哈希表,里边存储着SyncList结构的数据。SyncListSyncD* { f g 2 * ` T bata的联系如下图所示, 0 O V ! [ :

iOS探索 细数iOS中的那些锁
2.3 运用快速缓存
static Syncq A UData* id2data(id object, enum usaH j o C b . 6ge why)
{
...
#if SUPPORT_DIRECT_THREAD_KEY# x ( J E C 0S
/G H ! ! i & 5 e x/ Check per-thread single-entry fast cache for matching object
// 检查每线程单项快速缓存中是否有匹配的目标
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->obf H F f W =ject == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptf X U d + gr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->thrs a 5eadCount <= 0  ||  locl K 8 v rkCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIR. q g yECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KE a i 0 K T bEY, NUL1 s p g l f K - RL);
// atomic because maX . X M |y collide with concur) O / [ | 4 Yrent ACQUIRE
OSAtomis G ` HcDecrement32Barrier(&1 C ~ } H m d . K;result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
re] 3 qturn result;
}
}
#endif
...
}

这儿有个重要的知识点——i ( VTLSTLS全称为Thread Local Storage,在iOS中每个线程都具有自己的t ( 0 r I 0 ]TLS,负责保存本线程的一些变量, 且TLS= z O ) 8 g i l G无需锁保护

快速缓存的意义为:界说两个变量SYNC_DATA_DIRECT_KEY/SY$ & B M @NC_COU& ) lNT_DIRl ] A e * 7 5ECT_KEY,与tsl_get_direct/tls_set_direct合作能够从线程局部缓存中快速获得SyncCacheItem.dataSyncCacheItem.lockCount

假如在缓存中找到当时目标,, v 3就拿出当时被锁的次数lU u eockCount,再依据传入参数类型(获取、开释、检查)对lockCount别离P ^ . R S ` Y –进行操作

  • 获取资源ACQUIRElockCount++并依据key值存入被锁次数
  • 开释资源RELEASElockCount++并依据key值存入被锁次数。假如次数变为0,此刻锁也不复存在,需求从快速缓存移除并清空线程数threadCount
  • 检查资源check:不操作

lockCount表明被锁的次数,意味着能屡次进( 4 m J h入,从侧面表现出了递归性

2.4 获取该线程下的SyncCache

这个逻辑分支是找不到切当的线程符号只能进行一切R P 2 w 9 O 1的缓存遍; y n }

static A 0 I . q u ^c SyncData* id2data(id object, enum usi J u r Y 6 r g eage why)
{
...
SyncCache *caK $ f W 6 7 #che = fetch_cache(NO). z F r G ~;
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;I I L J ` K h 1
// Found a match.
result = it) H % /em->data;
if (result->threadCount <= 0  ||  item->lockCount <= 0) {
_objc_fatal("id{ E g - x g 4 )2data cache is bugg= & w 0 r _ b Dy");
}
switch(why) {
case ACQUIRE:
item->lo: 0 L =ckCount++z  3 i g + A =;
break;
case RELEASE:
itemS i / S a u 0->lock& G C F Z a } dCount--;
if (item->lockCount == 0) {
// remove from per-thi 2 Q f v Q # 9read cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collid- K s q Fe wiO h ) t Rth concurrent ACQUIRE
OSAtomicDecrement32Barrier(: I K&result->threadCount)* H ) D J W F l;
}
break;
cas] T s t Fe CHECK:
// do nothing
break;
}
return result;
}
}
...
}

这儿介绍一下SyncCacheSyncCacheItem

typedef struct {
SyncData *data;             //该缓存条目对应的SyncData
unsigned int lockCount;     //该目标在该线程中被加锁的次数
} SyncCacheItem;* 7 + 7 ( ~  r
typedef struct SyncCache {
unsigned int allocated;     //该缓存此刻对应的缓存巨细
ung % [ f & L rsigned int used;          //该缓存此刻对应的已运用缓存巨细
SyncCacheItem list[0];      //SyncCacheItem数组
} SyncCache;
  • SyncCacheItem用来记载某个SyncData在某个线程中被加锁的记B r : ^ c H N 7 载,一个SyncData能够被多个SyncCacheIteb . / G E F u Km持有
  • Sync% [ ZCache用来记载某个线程中一切Sye t U f [ DncCacheItem,而且记载了缓存巨细以及已运用缓存巨细
2.5 大局哈希表查找

快速、慢速流程都没找到缓存就会来到这步——在体系保存的哈希表进行链式查找

sq , * + D ftatic SyncData* id2data(id obC Y  bject, enum usage why)
{
...
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL) V M  Y; p =m P . ~ S , p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with cona R M * g u 3 Bcurrent RELEASE
OSAtomicIncro E I 9 {ement32Bam , Q 0 $rrier(&result-% $ h {  ~ 9 H>threadCount)q G Z Q S [;
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
/` R P y/ no SyncData currently assocC ` 6  A xiated wi- T n . [ { i Y Hth object
if ( (why == RELEAv u SE) || (why == CHECK) )
goto done;
// an unused o] # ? 6 _ Q R Rne was found, use it
if ( firstUnus$ F j H zed != NULL ) {
result = firstUnused;
result->object =y # v z O $ = l (obY : # . Z :jc_obj9 ] % I d @ z S 6ect *)object;
result-&gD ! P  , )t;threadCountv % t = 1;
goto done;
}
}
...
}
  1. lockp->lock()并不是在底层对锁进行了封装,而是在查找进程前后进行了加锁操作
  2. for循环遍历链表,假如有契合的就goto done

    • 寻找链表中未运用的SyncData并作符号
  3. 假如是RELEASE+ Y . qCHECK直接goto done
  4. 假如第二步中有发现; I – r j / p第一次运用的的目标就将threadCount符号为1且goto done
2.6 生成新数据并写入缓存
static SyncData* id2data(id object, enum usage why)
{
...
posix_memalign((void **)&r` h y J gesult, aligno! N m r .f(SyncDaq x 9ta), siz* J { U w A @ 5eof(SyncDatam d 9 s));
result->object = (objc_object *)object;
result-y t 9 ? 1 g n u>threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_q  W ! D g 6 ? Zlock);
result->nextDa9 Q e n N b O 9ta = *listp;
*listp = result;
done:
lockp->a I 4 9 5 * P;unlock();
if (result) {
// Only new ACQUIRE should geG # 1 : Gt here.
// All RELEASE and CHECK and recursive ACQUIRE a1 ? Q 4 m ^ +re 
// handled by the p@ C f ? *er-thread cache| W B F y v # S |s above.
if (why == RELEASE) {
// PrA - . 4 M _obably some thread is incorrectly exiting 
// while th[ [ 1 j P D Ue object is held by another thread.
return nil;7 e R C  w
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fat+ 3 u ! ~ E } O Aal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DAT R G ] 6 ` ) `TA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void8 0 y G [ ) 2*)1);n 5 I
} else
#endif
{
// Save in thread cache
if (!cac = 9 h B e - uc c yhe) cache = fetch_cache(YES);
cache->list[cache->9 s a D l o L;used].data = result;
cache->list[cache->used].lockCount =P x D 2 : 1;
cache-&gK 4 % A A It;used++;
}
}
...
}
  1. 第三步状况均不满= ) . k p 8 H e意(即链表不存在——目标关于全部线程来说是第z , J o d ?一次加锁)就会创建SyncData并存在result里,方便下次进行存储
  2. done剖析:
    • ! k # ~ B将前面的lock锁解开
    • 假如是RELEASE类型直接回来nil
    • ACb h ] i tQUIRE类型和目标的断言判断
    • !fastCacheOccupied分支表明支持快速缓存且快速缓存被占用了,将该SyncCacheI& U O q 8 1 R otem数据写入快速缓存中
    • 不然将该SyncCacheItem存入该线程对应的SyncCache

感谢 syx______ 提出的见解,关于 !fastCacheOccu9 $ – g L Mpied 能够看下谈论区大佬的解释

2.I n 6 z , A – 1 V7 疑难解答
  1. 不能运用非OC目标作为加锁条件——id2datX ^ } Ra中接纳参数为id类型
  2. 屡次锁同一个目标O Z # % v会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次目标
  3. 都说@synchronized功能: A s [ M ^ D低——是由于在底层增修改查H B p F $ D费了很多功能
  4. 加锁目标不能为nil,不然加锁无效,不能确保线程安全
- (void)test {
_testArray = [NSMutableArray a1 g m N j grray]X v J H G ^;
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (Y v - s 8 ; 2 /self.testArray) {
self.testArrayt H N = [NSMutableArray array];
}
});
}
}

上面代码一运转就会溃散,原因是由于在某一瞬间testArray开释了为nil,但哈希表中. [ a j ~存的目标也变成了nil,导致synchronized无效化

处理方案:

  • self进行同步锁~ H W 3 S * q ! 7,这个好像太臃肿了
  • 运用NSLock

3.NSLock

3.1 运用

NSLock| P Z + s K互斥锁的简略封装2 # 0 s G 4 (,运用如下:

- (void)test {
self.testArray = [NSMutableArray arraH 0 t n /y];
N: + _ K e  b HS# 3 j x = H bLock *lock = [[NSLock alloc] init];
for (int i = 0; i < 200000; ir c ` q ]++) {
dispatch_async(dispatch_getr z U M U *_global_queue(0, 0), ^{
[lock lock];
self.testArray = [NSMutableArray array];
[lock unlock];
});
}
}

NSLock在AFNetworking的AFURLSessionManage k Hr.m中有运用到

想要了解一下NSLock的底层原理,但发现其是在未开源的j Z E +Foundation源码下面的,但可是Swift对Foundation却开源了,能够在swift-cg } . N r Morelibs-foundation下载到源码来一探究竟

iOS探索 细数iOS中的那些锁

从源码来看便是对互斥锁的简略封装

3.2 留意事项

运用互斥锁NSLock异步B 6 [ o M l 4并发调用block块,block块内部递归调用自己,问打印什么?

- (void)tO M 0 y n F cest {
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dm q p $ y 0 + t mispatch_get_global_queue(0, 0), ^{
s` k Ntatic void (^block)(int);
block = ^(int value) {
NSLog(@"加锁前");
[lock lock];
NSLog(@"加锁h x H T -后");
if (value &G (  U ;gt; 0) {
NSLog(@"value——R [ * A%~ L ! $ j p Nd", value);
block(value - 1);
}
[lock unlock];
};
block(10);
});
}

输出成果并没有按代码表面P , ? 7 `的想法去走,而是只打印了一次value值

加锁前
加锁后
value——10
加锁前

原因: 互斥锁在递归调用时会形成堵塞,并非死锁——这儿的问题是后面的代码无法履行下o g d ` d {

  • 第一次加完锁之后还没出锁就进行递归调用
  • 第2次加锁就堵~ [ k ; [ p { 0塞了线程(由于不会查询缓存)

处理方案: 运用递归锁NSRecursiveLock替换NSLock

4.NSRecursiveLX | T Z i 1ock

4.1 运用

NSRecursiveLock运用和NSLock相似,如下代码就能处理上个问题

- (void)test {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] iE 8 a U + _ jnit];
dispao h V (tch_asyn^ a f ! v X D Ic(dispatch_get_gH d , e % A / x slobal_qe w Bueue(0, 0), ^{
static void (^block)(int);
block = ^(inta `  ? value) {
[lock lock];
if (value &B g p ^gt; 0) {
NSLog(@, |  k V"value——%dH q x K", value);
block(value - 1r p e q a % y 9);
}
[lock unlo( = ? s v 9ck];
};
block(10);
});
}

NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到

4.2 留意事项

递归锁在运用时需求留意死锁问题——前后代码相互等候便会发生死锁

上述代码在外层加个for循环,问输出成n | Q s G果?

- (void)test {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_globalb x u L a N j L |_queue(0, 0), ^{
static void (^block)(int);
block = ^(int value) {
[loK . E d sck lock];
if (value >y & g + U 0) {
NSLog(@"valG x O H  Q $ @ue——%d", value);f i g a
block(value - 1);
}
[lock unlock];
};
block(10);
});
}
}

运转代码会溃散,并会提示野指针过错

iOS探索 细数iOS中的那些锁

原因: for循环在bo e X v 6 * . ; alock内部对同一个目标进行了屡次锁操作,直到这个资源身上挂着N把锁,最后我们都无法一次性解锁——找_ V ; _ T n不到解锁的出口

即 线程1中加锁1、一起线程2中加锁2-> 解锁1等候解锁2 -> 解锁2等候解锁1 -> 无法完毕解锁/ v o ( r O L——形成= 6 U 0 ^ ) m死锁

处理: 能够选用运用缓存的@synC d | A Q t z k mchronized,由于它对目标进行锁操作,会先从缓存查找是否有锁syncData存在。假如有,直接回来而不加锁,确保锁的唯z + a , u #一性

5.dispatch_semaphore H & & Ae

在GCD运用篇章现已对信号量进行过讲解

6.NSCondition

NSCondition是一个条件锁,或许平常用的不多,但与信号量相似:线程1需求比及条件1满意U Q h E B A才会往下走,不然就会堵塞等候,直J w ) t至条件满意

相同的能在Swift源码中找到关于NSCondition部分

open class NSCondition: NSObject, NSLocking {
internal vM i ~ R |ar mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.all. ? @ocate(capacity: 1)
public override init() {
pth. ~ Y o Cread_mutex_iniS W ( s D  Rt(mutex, nil)
pthread_cond_init(cond, nil)
}
deinit {
pthread_mutex_destroy(mutex)
pthread7 / I F $_cond_9 - R * mdestroy(cond)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_muteQ 2 q K A H xx_unlo) R H ( k 5 X ?ck(mutex)
}
open func wait() {
pthread_cond_wait(cond, mutex# J k u 3 i H)
}
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) ei Y G D ; $ m L Zlse {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
opeA % U - - = e ]n func signal() {
pthread_cond_signal(cond)
}
open func bro[ Z % U r . f M Radcast() {
pthread_cond_broadcast(cond) // wait  signal
}
open var name: String?
}

从上述精简后的代码能q ( J q T x M [ e够得出以下几点:

  • NSCondition是对mutexcond的一种封装(conV 6 2 3 -d便是用于拜访和操作特定类型数据的指针)
  • wait操作会堵塞线程,使其进入休( 9 Q % G W . ;眠状态,直至超时
  • signal操作是唤醒一个正在休眠等候的线程
  • broadcast会唤醒一切正在等候的线程

7.NSConditionLock

顾名思义,便是NSCondition + Lock

那么和NSCondition的差异在于哪里呢?接下来看一下NSConV 7 S 1 K + B : DditionLock源码

open class NSConditionLock : NSObject, NSLocking {
internal var _cond =) e 7 B NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenk * j u ^ % 0 0 bience ov3 B y 7 f Y 9 +erride init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func uW H %  R | G o (nlock() {
_conN d (d.lock()
_thread = nil
_conU p :d.broadcast()
_cond.unlock()
}
open var condition: Int {
r- + n @ 7 3eturn _value
}
open fu+ c 6 -nc lock(whenCondition condition: Int) {
let _ = lock(whenConditiP : a , G s Son: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distan] / y a J ~ j TtPast)
}
open func tryLock(w{ 4 Z X k N 3henCondition condition: Int) ->? $ H & U 9 , Bool {
returl * J A a b I r ?n lock(wF J 7 @ m d . phenCondition: condition, before: Date.distantPast)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.% u $broadcast()
_cond.unlock(G ` P v)
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_selfH C (()
_cond.unlock()
return true
}
open func lock(whenC; , &ondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
while _thread != nit n o = U : tl ||h c : l 2 / N _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = ptht { T mread_self()
_cond.unlock()
return true
}
open var nak ^ / y { w mme: String?
}

从上述代码能够得出以下几点:

  • NSCondw c S ] ? x yitionLockNSCondition加线程数的封装
  • NSConditionL: ; Uock能够设置锁条件,而NSCondition仅仅无脑的通知信号

8.os_unfair_lock

由于OSSpinLock自旋锁的bug,代替方案是内部封装了os_unfair_lock,而os_unfair_lock在加锁时会处于休眠状态,而O X S不是自旋锁的忙等状态

9.互斥锁功能比照

iOS探索 细数iOS中的那些锁

四、总结

  • OSSpinLock不再安全,底层用os_unfair_lock代替
  • atomic只能确保setter、getter时线程安全,所以更多的运用nonatomic来润饰
  • 读写锁更多运J p { M ] 5 # w i用栅B ] + z = I门函数来完成
  • @sy{ k m +nchronized在底层保护了一个哈希链表进行data的存储,运用recursive_mutex_t进行加锁
  • NSLockNSRecursiveLockNSConditionNSConditionLock底层都是对pthread_mute) : { , x ` 1 Tx的封装
  • NSConditionNSConditionLog 4 c V [ kck是条件锁,当满意某一个条件时才干进行操作,和信号量dispatch_semaphore相似
  • 一般场景下涉及到线程安全,能够用NSLy r l 6 j 5ock
  • 循环调用时用NSRecursiveLock
  • 循环7 = L w w x A % B调用W B ] @ ] J | 5且有线程影响时,请留意死锁,假如有t g i 0死锁问题请运用@synchronized

写在后面

日常开发中若需求运用线程锁来确保线程安全,请多考虑一下再挑选运用哪个锁,@synchronized并不是最优的挑选。作为一名优秀的开发不但能让App正常运转,更要让它优质地运转、优化它的功能

参考资料

synchronized完成原理~ i [ ( w W Q 及缺点剖析

iOS底层学习 – 多线程之中的锁

iOS开发中的11种锁以及功能比照