多线程原理
进程
进程是指在系统中正在运行的一个应用程序。(A process is an executing instance of an application. )
每个进程之间是独立的,每个进程均运行在专用的且受保护的内存中
-
MAC用户可以通过“finder>服务>活动监视器”进行查看开启的进程信息
- 开启了QQ和微信后,系统会分别启动两个进程
iOS是单进程。也就是说app只能在自己的沙盒中运行,只能读取系统为自己app创建的文件夹AppData下的内容。不能随意跨过沙盒去访问其他app的沙盒信息。
线程
线程是进程中的一条执行路径。(A thread is a path of execution within a process.)
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
每个进程至少有一个线程。
一个进程可以对应多个线程。
一个iOS程序启动运行,会默认开启一条线程,这个线程称之为主线程。
任务
- 任务就是一段代码,要去执行这段代码,就需要在线程上执行。
线程与进程的关系
地址空间: 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间 的资源是独立的。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程 都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。 同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。
-
可以想象这么一个场景。在一个车间里main有多条生产线,但是呢,这个车间只有一个管理员。这些生产线同一时间只能运行一条生产线,而且全部生产线是有这一位管理员来控制开关。想要所有的生产线几乎同一时间完成任务的情况下,这位管理员只能是不间断的切换每条生产线的开关,已到达生产目的。这个车间就是“进程”,每天生产线就是“线程”。
多线程的生命周期
多线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡
处于运行中的线程,会被分配一段时间,称之为时间片(quantum)。现代操作系统在管理线程的时候,采用时间片轮转法,每个线程分配的时间大概在10~100毫秒左右。
一个线程创建成功,并且调用了start后,线程会进入到“可调度线程池”,分配时间片,进入到“就绪”状态,等待CPU的调用执行。如果一个线程的时间片用尽,就会被操作系统挂起,重新进入到“就绪”状态,等待新一轮的时间片分配。如果时间片没有用尽,但是需要等待事件的完成,就会进入“阻塞”状态。一个线程的任务完成或者被强制退出后,这个线程就是“死亡”状态。然后系统会从“就绪”状态的线程中,选择一个线程继续执行。
iOS 线程锁探索解析
本片文章主要是学习这些锁的用法,也会根据查到的关于锁的信息简单的说一下锁的实现,以便于后期自己的使用。如果对读者有帮助,善莫大焉。
iOS线程锁有@synchronized、NSLock、NSRecursiveLock、dispatch_semaphore、NSCondition、NSConditionLock、pthread_mutex、OSSpinLock等等。按照所得类型划分,大概有自旋锁、信号量、互斥锁、条件锁等。
自旋锁:OSSpinLock
互斥锁:NSLock、NSRecursiveLock、pthread_mutex
信号量:dispatch_semaphore
条件锁:NSCondition、NSConditionLock
自旋锁OSSpinLock
定义OSSpinLock
属性,并且初始化为OS_SPINLOCK_INIT
,这个值是个宏,值为0,#define OS_SPINLOCK_INIT 0
。OSSpinLock
按照惯例是解锁为0,加锁为非0。
加锁的方法:
OSSpinLockLock(&_ticketLock);
解锁的方法:
OSSpinLockUnlock(&_ticketLock);
#import "OSSpinLockModel.h"
#import <libkern/OSAtomic.h>
@interface OSSpinLockModel()
@property (nonatomic, assign) OSSpinLock ticketLock;
@property (nonatomic, assign) int ticketsCount;
@end
@implementation OSSpinLockModel
- (instancetype)init{
self = [super init];
if (self) {
///OSSpinLock的默认值,惯例是解锁为0,加锁为非0
self.ticketLock = OS_SPINLOCK_INIT;
[self ticketTest];
}
return self;
}
//多线程卖票
- (void)ticketTest{
self.ticketsCount = 10;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
[self sellingTickets];
}
});
}
}
//票数减少
- (void)sellingTickets{
OSSpinLockLock(&_ticketLock);
int oldMoney = self.ticketsCount;
sleep(.5);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"当前剩余票数-> %d", oldMoney);
OSSpinLockUnlock(&_ticketLock);
}
@end
打印结果:
2022-03-09 14:52:18.369933+0800 suanfaProject[5415:230517] 当前剩余票数-> 9
2022-03-09 14:52:18.370169+0800 suanfaProject[5415:230520] 当前剩余票数-> 8
2022-03-09 14:52:18.370275+0800 suanfaProject[5415:230517] 当前剩余票数-> 7
2022-03-09 14:52:18.370354+0800 suanfaProject[5415:230520] 当前剩余票数-> 6
2022-03-09 14:52:18.371039+0800 suanfaProject[5415:230523] 当前剩余票数-> 5
2022-03-09 14:52:18.371190+0800 suanfaProject[5415:230523] 当前剩余票数-> 4
2022-03-09 14:52:18.372228+0800 suanfaProject[5415:230522] 当前剩余票数-> 3
2022-03-09 14:52:18.372443+0800 suanfaProject[5415:230522] 当前剩余票数-> 2
2022-03-09 14:52:18.374568+0800 suanfaProject[5415:230519] 当前剩余票数-> 1
2022-03-09 14:52:18.374645+0800 suanfaProject[5415:230519] 当前剩余票数-> 0
在自旋锁中,线程会反复检查变量是否可用。由于线程这个过程中一直保持执行,所以是一种忙等待。 一旦获取了自旋锁,线程就会一直保持该锁,直到显式释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
自旋锁的实现原理,在拜读了大神的文章后,对自旋锁的实现原理有了较深的了解。把原文的自旋锁实现原理代码放上:
bool lock = false; // 一开始没有锁上,任何线程都可以申请锁
do {
while(test_and_set(&lock); // test_and_set 是一个原子操作
Critical section // 临界区
lock = false; // 相当于释放锁,这样别的线程可以进入临界区
Reminder section // 不需要锁保护的代码
}
//加上原子操作的处理。
//作用就是把target值设置为true,并返回原来的值。这样的话,能保证target使用一次过后,保持true的状态
bool test_and_set (bool *target) {
bool rv = *target;
*target = TRUE;
return rv;
}
OSSpinLock的线程安全问题
系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。详细内容请参考不再安全的 OSSpinLock
dispatch_semaphore
dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。
1.信号创建:dispatch_semaphore_t dispatch_semaphore_create(intptr_t value);
传入一个初始值来创建新的技术信号量,返回数据为
dispatch_semaphore_t
类型的信号量如果是处理多个线程协同处理一个特定的任务时,这个值可以传0
如果是要管理一个有限的资源池,传大于0的数值
2.等待信号:intptr_t dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待一个信号量,并且把信号量的值减小
其作用就是减少信号量的数值。如果信号量的数值小于0,这个函数会一直等待信号的发出。
dispatch_time_t:会为已经阻塞的线程提供一个超时时间。一旦信号量为0,该线程会被阻塞。阻塞时间超过了设置的时间,也会继续执行后续的代码
3.发送信号:intptr_t dispatch_semaphore_signal(dispatch_semaphore_t dsema);
发出一个信号量,并且把信号量的值增加
其作用是在增加信号量的数值。如果先前的信号量数值少于0,此函数会在返回之前唤醒等待的线程
- (void)semaphoreTest{
///信号量是GCD同步的一种方式,与其相关的三个函数是dispatch_semaphore_create dispatch_semaphore_wait dispatch_semaphore_singal
///当信号量的值只使用1/0的时候,可以作为一种线程锁,对线程进行加锁操作
///dispatch_semaphore_create这个函数创建信号量信息,传入一个值,返回是dispatch_semaphore_t类型的信号量
///dispatch_semaphore_wait 该参数会判断当前信号量是否为0,是0的话,阻塞当前线程。阻塞当前线程后,根据outTime来判 断是否执行后续代码。超过outTime后也会执行后续代码;如果信号量为1,就执行后续代码,并且把信号量的值减1
///dispatch_semaphore_singal方法,把信号量的值加1
///dispatch_semaphore是信号量,但当信号总量为1的时候,可以作为线程锁来使用
///在没有等待的情况下,他的性能比pthread_mutex要高,但是一旦有等待的情况 性能下降比较大
///相比于OSSpinLock,信号量的优势在于不会耗费CPU资源
//创建dispatch_semaphore_t
dispatch_semaphore_t dsema = dispatch_semaphore_create(1);
//创建超时时间
dispatch_time_t outTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
//创建子线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//信号量判断是否为大于0 ,是的话,执行后续操作,并且把信号量减1;
dispatch_semaphore_wait(dsema, outTime);
//执行代码
NSLog(@"子线程1,开始");
sleep(3);
NSLog(@"子线程1 ,结束");
//信号量加1
dispatch_semaphore_signal(dsema);
});
//创建子线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
//信号量判断是够大于0。是的话,执行后续操作,并且把信号量减1 ;否则的话,阻塞当前线程
dispatch_semaphore_wait(dsema, outTime);
NSLog(@"子线程2 开始");
dispatch_semaphore_signal(dsema);
});
}
设置的outTime少于线程1的休眠时间的打印结果:
2022-03-10 16:17:16.060334+0800 suanfaProject[4280:233736] 子线程1,开始
2022-03-10 16:17:18.065608+0800 suanfaProject[4280:233736] 子线程1 ,结束
2022-03-10 16:17:18.066031+0800 suanfaProject[4280:233737] 子线程2 开始
设置的outTime大于等于线程1的休眠时间的打印结果:
2022-03-10 16:28:31.992543+0800 suanfaProject[4421:240802] 子线程1,开始
2022-03-10 16:28:34.995799+0800 suanfaProject[4421:243715] 子线程2 开始
2022-03-10 16:28:35.993101+0800 suanfaProject[4421:240802] 子线程1 ,结束
互斥锁
所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程。
拥用着同一个互斥锁的线程,形成一个队列,按照先进先出的原则执行。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
互斥锁又分为两种情况,可递归和不可递归。
互斥锁在申请锁时,调用了 pthread_mutex_lock
方法,它在不同的系统上实现各有不同,有时候它的内部是使用信号量来实现,即使不用信号量,也会调用到 lll_futex_wait
函数,从而导致线程休眠。
pthread_mutex
1.初始化锁类型:
//初始化递归的参数
pthread_mutexattr_t attr;
int pthread_mutexattr_init(pthread_mutexattr_t *);
int pthread_mutexattr_settype(pthread_mutexattr_t *, int);
pthread_mutexattr_settype第二个参数是确定互斥锁的类型的参数,可选择属性值如下:
-
#define PTHREAD_MUTEX_NORMAL 0
- 普通锁,也是Mutex的默认锁。
- 不支持递归,一个线程对已经加锁的普通锁再次加锁,会引发死锁
- 对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果
-
#define PTHREAD_MUTEX_ERRORCHECK 1 //检错锁,
- 一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK
- 对一个已经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁操作返回EPERM
- NSLock的本质是这种检错锁
-
#define PTHREAD_MUTEX_RECURSIVE 2
- 支持递归,允许一个线程在释放锁之前多次对它加锁而不发生死锁
- NSRecursiveLock本质是这种锁
-
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
- 一个线程如果对一个已经加锁的默认锁再次加锁,或者虽一个已经被其他线程加锁的默认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果。
2.初始化方法:int pthread_mutex_init(pthread_mutex_t * __restrict, const pthread_mutexattr_t * _Nullable __restrict);
- 第一个参数是
pthread_mutex_t
类型的锁 - 第二个参数是定义锁的类型的参数
3.加锁方法:int pthread_mutex_lock(pthread_mutex_t *);
- 唯一的参数为需要加锁的锁变量
4.解锁方法:int pthread_mutex_unlock(pthread_mutex_t *);
- 唯一的参数为需要解锁的锁变量
加锁的代码:
-(void)pThreadMutex{
//初始化锁类型的参数
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
//初始化递归类型的锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// //初始化普通类型的锁
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//定义锁
__block pthread_mutex_t theLock;
//初始化锁
pthread_mutex_init(&theLock, &attr);
//销毁attr
pthread_mutexattr_destroy(&attr);
//线程,递归调用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//定义一个block
static void(^RecursiveMethod)(int);
RecursiveMethod = ^(int value){
pthread_mutex_lock(&theLock);
if (value > 0) {
NSLog(@"current value is %d",value);
sleep(1);
RecursiveMethod(value - 1);
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod(5);
});
}
NSLock
NSLock遵循NSLocking协议,NSLocking协议中是两个方法,加锁和解锁方法。
非递归锁,在同一个线程中,如果已经加了锁,再次进行加锁,会导致死锁。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NSLock类自定义了两个方法并且加上了一个名字的属性
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
//用来判断能否加锁。并不会阻塞线程
- (BOOL)tryLock;
//判断在date之前能否加锁,可以加锁的话,返回true,否则返回false。
- (BOOL)lockBeforeDate:(NSDate *)limit;
//锁的名字
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSLock锁的使用
- (void)nsLockTest{
//锁变量
NSLock *lock = [[NSLock alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"线程1");
sleep(2);
[lock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[lock lock];
NSLog(@"线程2");
[lock unlock];
});
}
NSRecursiveLock
NSCursiveLock是一种递归锁,所以这个锁可以被同一个线程多次请求,重复调用,而不会引起死锁。比如使用NSLock,lock以后,再次调用同一把锁就会引起死锁。
该锁主要用在循环或者递归操作中。
递归锁会追踪他被lock的次数。每次成功的lock,都必须平衡调用unlock操作。只有达到所有的锁全部被unlock,锁最后才能被释放。才能被其他的线程使用。
和NSLock一样遵循NSLocking协议。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NSRecursiveLock类自定义了两个方法并且加上了一个名字的属性
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSRecursiveLock的使用:
- (void)recursiveLcokTest{
//初始化递归锁
NSRecursiveLock * lock = [[NSRecursiveLock alloc] init];
// NSLock * lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void(^recursiveMethod)(int);
recursiveMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"value = %d",value);
sleep(1);
recursiveMethod(value - 1);
}
[lock unlock];
};
recursiveMethod(5);
});
}
NSCondition
条件锁,一个线程获得了锁,其它线程等待。
NSCondition的底层是通过条件变量(condition variable) pthread_cond_t 来实现的。一般pthread_cond_t,会搭配pthread_mutex_t 一起使用的。当锁住的共享变量发生改变时,可能需要通知相应的线程(因为可能该共享变量涉及到多个线程),这时就需要用到pthread_cond_t这种条件变量来精准的通知某个或几个线程, 让他们执行相应的操作。也就意味着条件锁NSCondition也是一种互斥锁。
那么NSCondition
到底做了什么呢?我们来看看源码,然而,Objective-C
代码并不能看到NSCondition
的具体实现。以下为swift版本的源码:
open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
deinit {
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
}
// 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,
// 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
open func lock() {
pthread_mutex_lock(mutex)
}
// 释放锁,与lock成对出现
open func unlock() {
pthread_mutex_unlock(mutex)
}
// 让当前线程处于等待状态,阻塞
open func wait() {
pthread_cond_wait(cond, mutex)
}
// 让当前线程等待到某个时间,阻塞
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
// 发信号告诉线程可以继续执行,唤醒线程
open func signal() {
pthread_cond_signal(cond)
}
open func broadcast() {
pthread_cond_broadcast(cond) // wait signal
}
open var name: String?
}
NSCondition相关方法:
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSCondition 的使用:
- (void)conditionTest{
NSCondition *condition = [[NSCondition alloc]init];
NSMutableArray *products = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
if (products.count == 0) {
NSLog(@"wait for product");
[condition wait];
}
[products removeObjectAtIndex:0];
NSLog(@"custome a product");
[condition unlock];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product,总量:%zi",products.count);
[condition signal];
[condition unlock];
sleep(1);
}
});
}
NSConditionLock
NSConditionLock
对NSCondition
又做了一层封装,自带探测条件,使用起来更加方便。
NSConditionLock的相关方法:
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSConditionLock的使用:
- (void)conditionLock{
//initWithCondition:默认条件为0。现在设置初始条件为1,这样的话线程1就满足条件,处理线程1的事情;否则线程1不满足条件,就从线程2开始处理
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];
__block int flag = 0;
NSInteger trueValue = 1;
NSInteger falseValue = 0;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[lock lockWhenCondition:trueValue];
flag += 1;
NSLog(@"~thread1~当前数值为:~ %d ~", flag);
sleep(1);
[lock unlockWithCondition:falseValue];
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[lock lockWhenCondition:falseValue];
flag -= 1;
NSLog(@"~thread2~当前数值为:~ %d ~", flag);
sleep(1);
[lock unlockWithCondition:trueValue];
}
});
}
@synchronized
@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改,保证代码的安全性。也就是包装这段代码是原子性的,安全的。@synchronized 支持递归锁。
描述@synchronized的实现原理:
@synchronized
block 会变成 objc_sync_enter
和 objc_sync_exit
的成对儿调用。
@synchronized(obj) {
// do work
}
会给转化成:
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
objc_sync_enter的方法实现。如果obj存在,通过id2data()函数拿到了data,调用data的mutex进行加锁。如果obj为nil,objc_sync_enter什么也不做。
//开始在obj上进行同步。如果有需要,会分配与obj关联的递归互斥锁。一旦获取锁成功返回OBJC_SYNC_SUCCESS。
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS; //OBJC_SYNC_SUCCESS=0
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
//加锁
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
//啥也不干,asm("");
objc_sync_nil();
}
return result;
}
objc_sync_exit的方法实现。
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
//这跟objc_sync_enter一样,只是参数变为了RELEASE
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
//tryUnlock使用lockdebug_recursive_mutex_unlock清理锁
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
与以上两个方法相关的结构体:
typedef struct SyncData {
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount;
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
// Use multiple parallel lists to decrease contention among unrelated objects.
#define COUNT 16
#define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1))
#define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
#define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
static SyncList sDataLists[COUNT];
SyncList中的SyncData使用单向链表结构进行链接。
当你调用 objc_sync_enter(obj)
时,它用 obj
内存地址的哈希值查找合适的 SyncData
,然后将其上锁。当你调用 objc_sync_exit(obj)
时,它查找合适的 SyncData
并将其解锁。
这里对@synchronized的原理只是简单说明,如需深入了解,请参考:@synchronized分析 和 关于 @synchronized,这儿比你想知道的还要多。
@synchronized的使用:
-(void)saleTickets{
while (YES) {
@synchronized(self){
sleep(1);
//1. 判断是否还有票
if (self.ticketCount > 0) {
//2. 如果还有票,卖一张,提示用户
self.ticketCount --;
NSLog(@"剩余票数 %ld",self.ticketCount);
}else{
//3. 如果没有票,退出循环
NSLog(@"没票了~~~");
break;
}
}
}
}
各种锁的对比
在 ibireme 的 不再安全的 OSSpinLock 一文中,有一张图片简单的比较了各种锁的加解锁性能,如果对时间要求比较高的,可以对比后使用比较符合业务要求的锁: