自旋鎖勋乾,互斥鎖杨何,讀寫鎖
自旋鎖就是一個(gè)忙等狀態(tài) do--while
互斥鎖就是一個(gè)閑等糕篇,可以使得CPU進(jìn)行休眠去做別的處理
下面是iOS中所用到的鎖:執(zhí)行10萬次所用的時(shí)間
OSSpinLock: 1.70 ms 自旋鎖 iOS10后被移除火诸,因?yàn)槠溆袃?yōu)先級(jí)占用躲查,蘋果用os_unfair_lock這個(gè)互斥鎖來代替被廢除的OSSpinLock鎖
dispatch_semaphore: 3.19 ms
pthread_mutex: 2.18 ms
NSCondition: 3.33 ms
NSLock: 2.93 ms
pthread_mutex(recursive): 3.08 ms
NSRecursiveLock: 4.66 ms
NSConditionLock: 10.82 ms
@synchronized: 9.09 ms
下面代碼打印后它浅,我們發(fā)現(xiàn)其并不是以49依次遞減的,這說明self.count這個(gè)是線程不安全的镣煮。如果改成atomic原子屬性姐霍,其也不會(huì)保證線程安全的。這個(gè)是因?yàn)? self.count --這句代碼的執(zhí)行,其實(shí)就是執(zhí)行了set和get方法镊折,這樣同時(shí)執(zhí)行原子屬性無法同時(shí)滿足線程安全黔衡,只滿足了一邊的線程安全。
解決這個(gè)問題就需要用到鎖功能腌乡。
@property (nonatomic ,assign) int count;
self.count = 50;
- (void)test {
for (int i = 0; i < 10; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.count --;
NSLog(@"%d",self.count);
});
}
}
2022-05-31 15:11:04.921282+0800 iOSLockDemo[4510:100720] 49
2022-05-31 15:11:04.921301+0800 iOSLockDemo[4510:100719] 47
2022-05-31 15:11:04.921291+0800 iOSLockDemo[4510:100732] 48
2022-05-31 15:11:04.921311+0800 iOSLockDemo[4510:100727] 46
2022-05-31 15:11:04.921334+0800 iOSLockDemo[4510:100733] 45
2022-05-31 15:11:04.921342+0800 iOSLockDemo[4510:100720] 44
2022-05-31 15:11:04.921355+0800 iOSLockDemo[4510:100734] 43
2022-05-31 15:11:04.921397+0800 iOSLockDemo[4510:100732] 42
2022-05-31 15:11:04.921410+0800 iOSLockDemo[4510:100719] 41
2022-05-31 15:11:04.921458+0800 iOSLockDemo[4510:100724] 40
下面是set屬性源碼:
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];//如果是原子操作就加一把spinlock_t鎖
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
下面是get屬性方法源碼:
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];//是原子就會(huì)加spinlock_t把鎖
slotlock.lock();//加鎖
id value = objc_retain(*slot);
slotlock.unlock();//解鎖
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
os_unfair_lock互斥鎖
當(dāng)加了os_unfair_lock鎖后發(fā)現(xiàn)其打印就是按順序進(jìn)行打印了
- (void)test {
for (int i = 0; i < 10; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self unfairLock_test];
});
}
}
-(void)unfairLock_test {
os_unfair_lock_lock(&_unfairLock);
self.count --;
NSLog(@"%d",self.count);
os_unfair_lock_unlock(&_unfairLock);
}
2022-05-31 16:38:19.268185+0800 iOSLockDemo[6256:161713] 49
2022-05-31 16:38:19.268249+0800 iOSLockDemo[6256:161718] 48
2022-05-31 16:38:19.268320+0800 iOSLockDemo[6256:161718] 47
2022-05-31 16:38:19.268413+0800 iOSLockDemo[6256:161713] 46
2022-05-31 16:38:19.268449+0800 iOSLockDemo[6256:161715] 45
2022-05-31 16:38:19.268492+0800 iOSLockDemo[6256:161712] 44
2022-05-31 16:38:19.268539+0800 iOSLockDemo[6256:161726] 43
2022-05-31 16:38:19.268621+0800 iOSLockDemo[6256:161717] 42
2022-05-31 16:38:19.268733+0800 iOSLockDemo[6256:161725] 41
2022-05-31 16:38:19.268998+0800 iOSLockDemo[6256:161719] 40
NSLock鎖
其是對(duì)Pthreds的封裝
- (void)test {
for (int i = 0; i < 10; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self nslock_test];
});
}
}
-(void)nslock_test {
[self.iLock lock];
self.count --;
NSLog(@"%d",self.count);
[self.iLock unlock];
}
NSConditon鎖
其是遵循了NSLocking協(xié)議,所以其有l(wèi)ock和unlock方法夜牡。
下面代碼可以模擬一個(gè)搶票的機(jī)制
- (void)nscondition_test {
for (int i = 0; i < 50; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self lg_production];
});
}
for (int i = 0; i < 100; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self lg_consumption];
});
}
}
- (void)lg_production {
[self.iCondition lock];
self.count ++;
NSLog(@"生產(chǎn)了一個(gè)產(chǎn)品,現(xiàn)有產(chǎn)品 : %d個(gè)",self.count);
[self.iCondition signal];//喚醒
[self.iCondition unlock];
}
- (void)lg_consumption {//這里依然會(huì)有負(fù)數(shù)打印与纽,這個(gè)是因?yàn)槭窍到y(tǒng)的bug,虛假喚醒
[self.iCondition lock];
while (self.count == 0) {//等待塘装,如果沒有這個(gè)會(huì)打印負(fù)數(shù)急迂,用while就會(huì)避免虛假喚醒
[self.iCondition wait];
}
self.count --;
NSLog(@"消費(fèi)了一個(gè)產(chǎn)品,現(xiàn)有產(chǎn)品: %d個(gè)",self.count);
[self.iCondition unlock];
}
NSConditonLock鎖
其是對(duì)NSConditon的又一次封裝,通過下面的鎖蹦肴,可以使得線程按1,2,3順序打印
- (void)lg_testConditonLock{
self.iConditionLock = [[NSConditionLock alloc] initWithCondition:3];//3個(gè)線程鎖
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:3];//加鎖
NSLog(@"線程 1");
[self.iConditionLock unlockWithCondition:2];//解鎖變成2
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:2];
NSLog(@"線程 2");
[self.iConditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.iConditionLock lockWhenCondition:1];
NSLog(@"線程 3");
[self.iConditionLock unlockWithCondition:0];
});
}
NSRecursiveLock鎖
這個(gè)是個(gè)遞歸鎖僚碎,這個(gè)只能保證在同一個(gè)線程里操作
鎖在同一時(shí)刻只能被一條線程擁有
遞歸鎖同一時(shí)刻能被多條線程鎖擁有
-(void)recursiveLock_test {
[self.iRecursiveLock lock];
self.count --;
NSLog(@"%d",self.count);
[self.iRecursiveLock unlock];
}
- (void)test {
for (int i = 0; i < 10; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self recursiveLock_test];
});
}
}
- (void)recursiveTest {
如果這里加了for循環(huán)進(jìn)行幾次,每次調(diào)用下面的代碼就會(huì)出現(xiàn)奔潰阴幌。這里是因?yàn)榧恿薴or就會(huì)有多個(gè)線程勺阐,遞歸鎖同一時(shí)刻能被多條線程鎖擁有,但是在解鎖的時(shí)候沒有按順序就會(huì)奔潰矛双,如果要解決這個(gè)問題可以添加 @synchronized這個(gè)鎖就可以解決渊抽。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^recursiveMethod)(int);
recursiveMethod = ^(int value){//block的遞歸調(diào)用,遞歸操作需要用到self.iRecursiveLock這種遞歸鎖
if (value > 0) {
[self.iRecursiveLock lock];
NSLog(@"%d",value);
recursiveMethod(value - 1);
[self.iRecursiveLock unlock];
}
};
recursiveMethod(10);
});
}
pthread_mutex鎖
其是c語言底層寫的
- (void)lg_pthread_mutex {
//非遞歸
pthread_mutex_t lock0;
pthread_mutex_init(&lock0, NULL);
pthread_mutex_lock(&lock0);
pthread_mutex_unlock(&lock0);
pthread_mutex_destroy(&lock0);
//遞歸
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
}
讀寫鎖
讀和寫互斥议忽,多讀單寫懒闷,下面我們用GCD來實(shí)現(xiàn)
- (NSString *)lg_read {
// 異步讀取
__block NSString *ret;
dispatch_sync(self.iQueue, ^{
// 讀取的代碼
ret = self.dataDic[@"name"];
});
NSLog(@"%@",ret);
return ret;
}
- (void)lg_write: (NSString *)name {
// 寫操作
dispatch_barrier_async(self.iQueue, ^{
[self.dataDic setObject:name forKey:@"name"];
});
}
@synchronized鎖
首先在main函數(shù)里添加 @synchronized (obj) 這個(gè)方法,然后用clang命令可以打印出main.cpp文件栈幸。
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
NSObject *obj = [NSObject alloc];
@synchronized (obj) {
}
}
return NSApplicationMain(argc, argv);
}
下面是得到的cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
{ id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}//~_SYNC_EXIT() 是一個(gè)析構(gòu)函數(shù)
id sync_exit;
}
_sync_exit(_sync_obj);//結(jié)構(gòu)體_SYNC_EXIT的構(gòu)造函數(shù)
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}
}
return NSApplicationMain(argc, argv);
}
其實(shí)在synchronized里有用到下面兩個(gè)函數(shù)愤估,objc_sync_exit和objc_sync_enter。這里的SyncData是一個(gè)單向的鏈表速址。其中其里面有個(gè)spinlock_t玩焰,這個(gè)其實(shí)就是 os_unfair_lock鎖。
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// 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->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
下面的StripedMap是用來緩存帶spinlock鎖能力的類或者結(jié)構(gòu)體壳繁,如果一張表存儲(chǔ)一個(gè)那么會(huì)浪費(fèi)內(nèi)存震捣,如果一張表存儲(chǔ)多個(gè)那么效率會(huì)很低,所以就執(zhí)行設(shè)置特定的個(gè)數(shù)StripeCount = 8(非模擬器上) 或者StripeCount = 64 (模擬器上)闹炉。其實(shí)這個(gè)SyncList里是存著sDataLists這樣的數(shù)據(jù)形式蒿赢。
在線程的局部存儲(chǔ)空間里也就是TLS,其里面緩存有synchronized的信息.也就是線程里有單獨(dú)開辟了一小部分空間讓其存儲(chǔ)一些關(guān)于線程的數(shù)據(jù)渣触。
static StripedMap<SyncList> sDataLists;
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
static SyncCache *fetch_cache(bool create)
{
_objc_pthread_data *data;
data = _objc_fetch_pthread_data(create);
if (!data) return NULL;
if (!data->syncCache) {
if (!create) {
return NULL;
} else {
int count = 4;
data->syncCache = (SyncCache *)
calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
data->syncCache->allocated = count;
}
}
下面是_objc_pthread_data 這個(gè)數(shù)據(jù)結(jié)構(gòu)體里存syncCache信息羡棵,這里就是關(guān)于synchronize的信息。
typedef struct {
struct _objc_initializing_classes *initializingClasses; // for +initialize
struct SyncCache *syncCache; // for @synchronize
struct alt_handler_list *handlerList; // for exception alt handlers
char *printableNames[4]; // temporary demangled names for logging
const char **classNameLookups; // for objc_getClass() hooks
unsigned classNameLookupsAllocated;
unsigned classNameLookupsUsed;
// If you add new fields here, don't forget to update
// _objc_pthread_destroyspecific()
} _objc_pthread_data;
下面SyncCache里是存著SyncCacheItem嗅钻,在SyncCacheItem里有l(wèi)ockCount皂冰,這個(gè)是記錄當(dāng)前線程的加鎖次數(shù)
typedef struct {
SyncData *data;
unsigned int lockCount; // number of times THIS THREAD locked this block
} SyncCacheItem;
下面是加鎖的時(shí)候lockCount會(huì)加1店展,解鎖的時(shí)候lockCount會(huì)減1,下面的源碼也就是在線程的緩存里去找和我們這個(gè) 加鎖的對(duì)象SyncData秃流,找到的話就會(huì)返回赂蕴。這里有快速的緩存,去查找拿取舶胀,沒有的話就去objc_pthresd_data里正常查找概说。如果還沒有就會(huì)去表里找(StripeCount數(shù)量表中),如果還沒有那就會(huì)創(chuàng)建一個(gè)添加到表里嚣伐。
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// 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->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 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_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
下面用一個(gè)圖示來顯示整個(gè)過程:
其中l(wèi)ockcount是指當(dāng)前線程的加鎖數(shù)糖赔,thredcount是指當(dāng)前鎖被幾條線程所擁有。