在使用多线程技术的时候,往往会遇到多个线程访问同一块资源,导致资源数据错乱,如下例:
- 例1
-(void)saleTicket
{
int oldTicketCount = self.ticktCount;
sleep(.2);
oldTicketCount -- ;
self.ticktCount = oldTicketCount;
NSLog(@"还剩%d张票-%@",oldTicketCount,[NSThread currentThread]);
}
-(void)ticketTest
{
self.ticktCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
}
打印结果:
2020-12-01 12:40:49.649515+0800 多线程的安全隐患1[21018:196122] 还剩14张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649521+0800 多线程的安全隐患1[21018:196123] 还剩14张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649533+0800 多线程的安全隐患1[21018:196129] 还剩14张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649664+0800 多线程的安全隐患1[21018:196123] 还剩12张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649664+0800 多线程的安全隐患1[21018:196129] 还剩12张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649666+0800 多线程的安全隐患1[21018:196122] 还剩13张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649741+0800 多线程的安全隐患1[21018:196123] 还剩11张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649754+0800 多线程的安全隐患1[21018:196129] 还剩10张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649778+0800 多线程的安全隐患1[21018:196122] 还剩9张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649810+0800 多线程的安全隐患1[21018:196123] 还剩8张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649946+0800 多线程的安全隐患1[21018:196129] 还剩7张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.650073+0800 多线程的安全隐患1[21018:196123] 还剩6张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.650184+0800 多线程的安全隐患1[21018:196122] 还剩5张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.650297+0800 多线程的安全隐患1[21018:196129] 还剩4张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.650413+0800 多线程的安全隐患1[21018:196122] 还剩3张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
- 例2
#pragma mark - 存钱取钱
//取钱
-(void)drawMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取出20,还剩%d-%@",oldMoney,[NSThread currentThread]);
}
//存钱
-(void)saveMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存入50,还剩%d-%@",oldMoney,[NSThread currentThread]);
}
-(void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
打印结果
2020-12-01 13:05:04.321877+0800 多线程的安全隐患1[21392:215326] 取出20,还剩80-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.321882+0800 多线程的安全隐患1[21392:215328] 存入50,还剩150-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.321989+0800 多线程的安全隐患1[21392:215326] 取出20,还剩130-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.321994+0800 多线程的安全隐患1[21392:215328] 存入50,还剩180-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322061+0800 多线程的安全隐患1[21392:215326] 取出20,还剩160-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322085+0800 多线程的安全隐患1[21392:215328] 存入50,还剩210-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322137+0800 多线程的安全隐患1[21392:215326] 取出20,还剩190-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322145+0800 多线程的安全隐患1[21392:215328] 存入50,还剩240-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322278+0800 多线程的安全隐患1[21392:215326] 取出20,还剩220-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322467+0800 多线程的安全隐患1[21392:215328] 存入50,还剩270-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322580+0800 多线程的安全隐患1[21392:215326] 取出20,还剩250-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322774+0800 多线程的安全隐患1[21392:215328] 存入50,还剩300-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322887+0800 多线程的安全隐患1[21392:215326] 取出20,还剩280-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322996+0800 多线程的安全隐患1[21392:215328] 存入50,还剩330-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.323105+0800 多线程的安全隐患1[21392:215326] 取出20,还剩310-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.457707+0800 多线程的安全隐患1[21392:215328] 存入50,还剩360-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.457741+0800 多线程的安全隐患1[21392:215326] 取出20,还剩340-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.457880+0800 多线程的安全隐患1[21392:215328] 存入50,还剩390-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.457941+0800 多线程的安全隐患1[21392:215326] 取出20,还剩370-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.458034+0800 多线程的安全隐患1[21392:215328] 存入50,还剩420-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
造成结果错乱的原因主要由下图进行解释,多个线程同时对同一块资源进行操作。
解决方案:线程同步(协同步调,按照预定的先后顺序)技术
常用的线程同步技术:锁🔒
iOS中的线程同步方案:(性能从高到低排序)
- os_unfair_lock
- OSSpinkLock 自旋锁(已过期)
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
OSSpinkLock-(自旋锁)等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
- 目前已经不再安全,可能会出现优先级反转问题
- 如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
- 需要导入头文件#import <libkern/OSAtomic.h>
优先级反转:如线程thread1优先级较高,线程thread2优先级较低,如果出现线程thread2先加了锁,那thread1的状态就是未解锁,处于忙等状态,但是由于thread1的优先级较高,CPU可能会分配大量资源给thread1,就会导致优先级低的thread2不能解锁。现在已经弃用了OSSpinkLock。
//初始化锁
self.lock = OS_SPINLOCK_INIT;//需要使用同一把锁,所以用属性保存这把锁
//加锁
OSSpinLockLock(&_lock);
//解锁
OSSpinLockUnlock(&_lock);
//尝试加锁
BOOL result = OSSpinLockTry(&_lock);
- 例1和例2中加锁OSSpinkLock
#pragma mark - 存钱取钱
//取钱
-(void)drawMoney
{
//加锁
OSSpinLockLock(&_moneyLock);
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取出20,还剩%d-%@",oldMoney,[NSThread currentThread]);
//解锁
OSSpinLockUnlock(&_moneyLock);
}
//存钱
-(void)saveMoney
{
//加锁
OSSpinLockLock(&_moneyLock);
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存入50,还剩%d-%@",oldMoney,[NSThread currentThread]);
//解锁
OSSpinLockUnlock(&_moneyLock);
}
-(void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
#pragma mark - 卖票
-(void) saleTicket
{
//加锁
OSSpinLockLock(&_ticketLock);
int oldTicketCount = self.ticktCount;
sleep(.2);
oldTicketCount -- ;
self.ticktCount = oldTicketCount;
NSLog(@"还剩%d张票-%@",oldTicketCount,[NSThread currentThread]);
//解锁
OSSpinLockUnlock(&_ticketLock);
}
-(void)ticketTest
{
self.ticktCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self sellTicket];
}
});
}
os_unfair_lock
- 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态
- 使用的时候需要导入头文件#import <os/lock.h>
//初始化锁
self.ticketLock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&_ticketLock);
//解锁
os_unfair_lock_unlock(&_ticketLock);
pthread_mutex
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //递归锁
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
/*
- mutex叫做“互斥锁”,等待锁的过程会处于休眠状态
- 需要导入头文件#import <pthread.h>
//静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//初始化锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_init(&lock, &attr);
//加锁
pthread_mutex_lock(&_ticketLock);
//解锁
pthread_mutex_unlock(&_ticketLock);
//销毁相关资源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&lock);
pthread_mutex递归锁
//递归锁:允许同一个线程对同一个锁重复加锁
//初始化锁的属性
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_cond_init(&_cond, NULL);
//唤醒等待_cond条件的线程
pthread_cond_signal(&_cond);//信号
//激活所有等待这个条件的线程
pthread_cond_broadcast(&_cond);//广播
pthread_mutex的条件使用
- 写一个通过多线程操锁添加数组元素和删除数组元素的方法(删除元素的时候必须保证数组中是有元素的)代码如下:
#import "MutexDemo3.h"
#import <pthread.h>
@interface MutexDemo3()
@property (nonatomic,assign)pthread_mutex_t mutex;
@property (nonatomic,strong)NSMutableArray *data;
@property (nonatomic,assign)pthread_cond_t cond;
@end
@implementation MutexDemo3
- (instancetype)init
{
self = [super init];
if (self) {
//静态初始化
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
self.mutex = [self __initMutexWith:_mutex];
}
return self;
}
-(pthread_mutex_t)__initMutexWith:(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_cond_init(&_cond, NULL);
self.data = [NSMutableArray array];
return lock;
}
//删除数组中的元素
-(void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
//等待条件(休眠并放开_mutex这把锁)
//如果等待到条件,会继续先加锁_mutex
pthread_cond_wait(&_cond,&_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
}
//往数组中添加元素
-(void)__add
{
pthread_mutex_lock(&_mutex);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
//唤醒等待_cond条件的线程
pthread_cond_signal(&_cond);//信号
// pthread_cond_broadcast(&_cond);//广播(激活所有等待这个条件的线程)
pthread_mutex_unlock(&_mutex);
}
-(void)otherTest
{
[[[NSThread alloc]initWithTarget:self selector:@selector(__remove) object:nil]start];
sleep(1.0);
[[[NSThread alloc]initWithTarget:self selector:@selector(__add) object:nil]start];
}
- (void)dealloc
{
//销毁相关资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
@end
NSLock和NSRecursiveLock
- NSLock是mutex普通锁的封装
- NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NSCondition
- NSCondition是对mutex和cond的封装
- 可用于生产者-消费者模式
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
NSConditionLock
- NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
@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
- 例:
- (instancetype)init
{
self = [super init];
if (self) {
//若初始化的时候不设置条件值,默认为0
// self.lock - [[NSConditionLock alloc]init];
self.lock = [[NSConditionLock alloc]initWithCondition:1];
}
return self;
}
-(void)__one
{
[self.lock lockWhenCondition:1];
NSLog(@"__one");
[self.lock unlockWithCondition:2];
}
-(void)__two
{
[self.lock lockWhenCondition:2];
NSLog(@"__two");
[self.lock unlockWithCondition:3];
}
-(void)__three
{
[self.lock lockWhenCondition:3];
NSLog(@"__three");
[self.lock unlock];
}
-(void)otherTest
{
[[[NSThread alloc]initWithTarget:self selector:@selector(__one) object:nil]start];
sleep(1.0);
[[[NSThread alloc]initWithTarget:self selector:@selector(__two) object:nil]start];
[[[NSThread alloc]initWithTarget:self selector:@selector(__three) object:nil]start];
}
dispatch_queue(DISPATCH_QUEUE_SERIAL)
直接使用GCD的串行队列,也是可以实现线程同步的
例:
#import "SerialQueueDemo.h"
@interface SerialQueueDemo()
@property (nonatomic,strong)dispatch_queue_t ticketQueue;
@property (nonatomic,strong)dispatch_queue_t moneyQueue;
@end
@implementation SerialQueueDemo
- (instancetype)init
{
self = [super init];
if (self) {
self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
self.moneyQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)__saveMoney
{
dispatch_sync(self.moneyQueue, ^{
[super __saveMoney];
});
}
- (void)__drawMoney
{
dispatch_sync(self.moneyQueue, ^{
[super __drawMoney];
});
}
- (void)__saleTicket
{
dispatch_sync(self.ticketQueue, ^{
[super __saleTicket];
});
}
@end
dispatch_semaphore
- semaphore:信号量
- 信号量的初始值,可以用来控制线程
- 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//信号量的初始值
int value = 5;
//初始化信号量
self.semaphore = dispatch_semaphore_create(value);
//如果信号量的值 >0,就让心好凉的值减1,然后继续往下执行
//如果信号量的值是<=0,就会休眠等待,直到信号量的值>0,就让信号量的值减1,然后继续往下执行
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
#import "SemaphoreDemo.h"
@interface SemaphoreDemo()
@property (nonatomic,strong)dispatch_semaphore_t semaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init
{
self = [super init];
if (self) {
self.semaphore = dispatch_semaphore_create(5);
}
return self;
}
-(void)otherTest
{
for (int i = 0; i < 20; i++) {
[[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]start];
}
}
-(void)test
{
//如果信号量的值 >0,就让信号量的值减1,然后继续往下执行
//如果信号量的值是<=0,就会休眠等待,直到信号量的值>0,就让信号量的值减1,然后继续往下执行
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@",[NSThread currentThread]);
dispatch_semaphore_signal(self.semaphore);
}
@end
- 通过宏定义信号量锁
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t oneceToken; \
dispatch_once(&oneceToken, ^{ \
semaphore = dispatch_semaphore_create(1); \
});\
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
@synchronized
- @synchronized是对mutex递归锁的封装
- 源码查看:objc4中的objc- sync.mm文件
- @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁解锁操作。
源码:
- 调用int objc_sync_enter(id obj):
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
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");
}
objc_sync_nil();
}
return result;
}
- SyncData
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;
- recursive_mutex_tt:
template <bool Debug>
class recursive_mutex_tt : nocopy_t {
os_unfair_recursive_lock mLock;
public:
//初始化为递归锁
constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
lockdebug_remember_recursive_mutex(this);
}
constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
: mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
{ }
//加锁
void lock()
{
lockdebug_recursive_mutex_lock(this);
os_unfair_recursive_lock_lock(&mLock);
}
//解锁
void unlock()
{
lockdebug_recursive_mutex_unlock(this);
os_unfair_recursive_lock_unlock(&mLock);
}
- 使用方法
#import "SynchronizedDemo.h"
@implementation SynchronizedDemo
- (void)__saveMoney
{
@synchronized(self){
[super __saveMoney];
}
}
- (void)__drawMoney
{
@synchronized(self){
[super __drawMoney];
}
}
- (void)__saleTicket
{
static NSObject *lock;
static dispatch_once_t oneceToken;
dispatch_once(&oneceToken, ^{
lock = [[NSObject alloc]init];
});
@synchronized(lock){
[super __saleTicket];
}
}
//递归
-(void)otherTest
{
@synchronized ([self class]) {
NSLog(@"123");
[self otherTest];
}
}
@end
产生死锁的四个必要条件:
- 一个资源只能被一个进程占用
- 某个进程占用了资源,就只能他自己去释放。
- 某个进程之前申请了资源,我还想再申请资源,之前的资源还是我占用着,别人别想动。除非我自己不想用了,释放掉。
- 一定会有一个环互相等待。
自旋锁和互斥锁
-
通过跟踪OSSpinkLock(自旋锁)的汇编代码,可以看到如下_OSSpinLockLockSlow中,一直在进行循环,即线程等待解锁的过程
-
通过跟踪pthread_mutex(互斥锁)可以看到在进入__psynch_mutexwait函数时,会调用syscall,这个时候再在控制台输入si(一步汇编一步汇编的执行)时,断点就会过掉---即线程休眠了。通过跟踪os_unfair_lock也是这样,所以可以猜测os_unfair_lock也是互斥锁。
什么情况使用自旋锁比较好?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
什么情况使用互斥锁比较好?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
atomic
- atomic用于保证属性setter和getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
- 可以参考源码objc4的objc- accessors.mm
- 它并不能保证使用属性的过程是线程安全的
一般在Mac上使用比较多,iOS设备使用比较少,因为比较消耗性能
源码:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
//noatomic
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
//atomic
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) {//nonatomic
oldValue = *slot;
*slot = newValue;
} else {//atomic
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
iOS中的读写(IO)安全方案
多读单写
- 同一时间,只能有1个线程进行写操作
- 同一时间,允许多个线程进行读的操作
- 同一时间,不允许既有写的操作,又有读的操作
实现方案: - pthread_rwlock:读写锁
- dispatch_barrier_async:异步栅栏调用
pthread_rwlock读写锁
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic,assign)pthread_rwlock_t lock;
@end
@implementation ViewController
/**
同一时间,只能有1个线程进行写操作
同一时间,允许多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作
*/
- (void)viewDidLoad {
[super viewDidLoad];
//初始化锁
pthread_rwlock_init(&_lock, NULL);
for (int i = 0; i < 50; i++) {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
//多读单写
//读操作应该可以多条线程同时读取
-(void)read
{
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"read - begin,%@",[NSThread currentThread]);
pthread_rwlock_unlock(&_lock);
NSLog(@"read - end,%@",[NSThread currentThread]);
}
//同一时刻只能允许一条线程写入操作
-(void)write
{
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"write - begin,%@",[NSThread currentThread]);
pthread_rwlock_unlock(&_lock);
NSLog(@"write - end,%@",[NSThread currentThread]);
}
打印结果截取:从结果中可以看到,有多条线程同时读的操作,但是没有同时写和同时读的操作
dispatch_barrier_async
- 这个函数传入的并发队列必须是自己通过dispatch_queue_create创建的
- 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
//初始化队列
self.queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
//读
dispatch_async(self.queue, ^{
};
//写
dispatch_barrier_async(self.queue, ^{
};
- 例:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
[self read];
[self read];
[self write];
}
}
-(void)read
{
dispatch_async(self.queue, ^{
NSLog(@"read-begin-%@",[NSThread currentThread]);
sleep(1);
NSLog(@"read-end-%@",[NSThread currentThread]);
});
}
-(void)write
{
dispatch_barrier_async(self.queue, ^{
NSLog(@"write-begin-%@",[NSThread currentThread]);
sleep(1);
NSLog(@"write-end-%@",[NSThread currentThread]);
});
}
@end